Skip to content

Shared full-height WP admin layout: replace per-plugin hard-coded calc(100dvh - 96px) heights (breaks under third-party admin banners) #487

Description

@OjasAT2105

Bug class (org-wide)

Every Sure-family admin SPA hard-codes the vertical overhead above its app and sizes itself with viewport math like calc(100dvh - 96px) (desktop) / calc(100dvh - 110px) (mobile). The constants assume exactly two things above the app: the WP admin bar (32px / 46px) and the plugin's own 64px topbar.

That assumption is false whenever ANY other plugin injects content into the admin above our app (in_admin_header, unclassed banners, promo bars). The math does not know the banner exists, so the app overflows the viewport and its bottom controls (Save button, sidebar items) are pushed off-screen.

Real-world impact: SureCookie support ticket 1507247 - MemberPress Courses' admin banner clipped SureCookie's Save button with no way to scroll to it; the customer's only workaround was browser zoom-out.

Confirmed occurrences of the same copy-forwarded pattern:

  • surecookie: src/admin/AdminApp.jsx - grid-rows-[64px_calc(100dvh_-_96px)] + h-[calc(100vh-64px)] (fixed height, worst symptom: content clipped, unreachable) - fixed via mechanism 2 below
  • surerank: src/apps/admin-components/layout/sidebar-layout.js:619 - min-h-[calc(100dvh_-_96px)] / 110px
  • suremails: src/App.js:37 - min-h-[calc(100dvh_-_96px)] / 110px
  • reported also in sureforms and surecontact (same template lineage)

Root cause: the admin layout was copied plugin-to-plugin instead of living in the shared library, so the wrong constant shipped everywhere. A build-time constant can never be correct here - other plugins' banner heights are unknowable.

Proper fix - two technically sound mechanisms

Mechanism 1 - flow-based, CSS-only (recommended for the shared component):
Scoped to the plugin's own admin screen (body class), turn the WP containers into a flex column and let the app take the remaining space:

body.toplevel_page_[slug] #wpcontent,
body.toplevel_page_[slug] #wpbody,
body.toplevel_page_[slug] #wpbody-content {
    display: flex;
    flex-direction: column;
    padding: 0;
}
body.toplevel_page_[slug] #wpcontent {
    min-height: calc(100vh - var(--wp-admin--admin-bar--height, 32px));
}
.[app-root] {
    flex: 1 1 auto;
    min-height: 0;
}

WP core owns --wp-admin--admin-bar--height (32px, 46px under 783px - wp-includes/css/admin-bar.css), so the only remaining constant is maintained by core itself. Injected banners become normal flex items: they take their natural height, the app takes the rest, and a dismissed banner reflows automatically with zero JS. Needs a QA pass for float-era markup (#wpfooter, screen-meta) inside the flex column, scoped per plugin page only.

Mechanism 2 - measured offset (JS, shipped in SureCookie as reference):
Measure the app container's real document offset into a CSS custom property and use it in the height calcs; re-measure via ResizeObserver on the parent + window resize (banners render late, resize, or get dismissed):

const compute = () => {
    const top = Math.max( 0, Math.round( node.getBoundingClientRect().top + window.scrollY ) );
    node.style.setProperty( '--admin-top', `${ top }px` );
};
// heights: calc(100dvh - <own topbar> - var(--admin-top, 32px))

Reference implementation: surecookie src/admin/AdminApp.jsx (ticket 1507247 fix). Verified against a simulated 72px in_admin_header banner.

Proposal: fix once in force-ui, adopt everywhere

  1. Ship an AdminPageLayout (topbar slot + optional sidebar slot + content slot) in force-ui that implements one of the mechanisms internally - so no consumer ever writes viewport math again. Minimally: export a documented useWpAdminOffset() hook + layout recipe.
  2. Migrate surecookie, surerank, sureforms, suremails, surecontact, suredash to the shared component and delete the per-plugin calc(100dvh - 96px) grids.

Acceptance criteria

  • With a 72px banner injected via in_admin_header, every plugin's admin app shows all controls (Save, full sidebar) without zooming.
  • Dismissing the banner reclaims the space without reload.
  • Mobile admin bar (46px at <=782px) handled without a separate hard-coded variant.

Repro harness (drop in mu-plugins):

add_action( 'in_admin_header', function () {
    echo '<div style="height:72px;background:#fde68a;">Simulated third-party banner</div>';
} );

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions