Skip to content

fix(nav): header menu popover renders off-screen on 404 pages#423

Merged
simonhamp merged 2 commits into
mainfrom
fix-404-popover-offscreen
Jul 10, 2026
Merged

fix(nav): header menu popover renders off-screen on 404 pages#423
simonhamp merged 2 commits into
mainfrom
fix-404-popover-offscreen

Conversation

@simonhamp

Copy link
Copy Markdown
Member

Problem

Opening the hamburger menu in the navigation header on a 404 page (any missing URL) at desktop widths shows nothing — the popover renders completely off-screen to the left.

Root cause

The popover's horizontal position came from an x-bind:style binding on the popover element that measured $refs.menuButton.getBoundingClientRect().right to compute a right: offset.

That binding evaluates synchronously during Alpine's init walk, but <body> carries x-cloak — and Alpine removes x-cloak in a deferred microtask after init completes. So the measurement runs while the entire page is display: none: every rect reads 0 and the popover gets right: <viewportWidth - 6>px (measured: left edge at −442px on a 1440px viewport).

The binding's only reactive dependency was width (body width via x-resize), so it never re-evaluated until something changed the body's width. Content-heavy pages tend to get such a nudge after load (e.g. a layout-affecting scrollbar appearing) and silently self-heal; the short, static 404 page never does — which is why the bug reproduced reliably there. The vertical top was unaffected because it was already set in an open-time handler.

Fix

  • Remove the init-time x-bind:style binding.
  • Compute both top and right in the existing open-time updater (updatePopoverTopupdatePopoverPosition), which runs on the popover toggle event — by then layout is real and measurable. Below lg it clears the inline right so the CSS mobile insets apply.
  • Reposition on resize as well as scroll while open (listeners added on open, removed on close), so the popover stays anchored, including when resizing across the lg breakpoint while open.

Verification

Driven in headless Chrome (Playwright) against the local Herd site at 1440×900:

Scenario Before After
First open on fresh 404 page load right: 1434px → left edge at −442px (off-screen) top: 125px; right: 84px — anchored under the button
Home / blog first open same stale right at init correct on first open
Mobile (390px) inline top only; CSS insets position the sheet
Open at 1440px, resize to 800px inline right cleared, mobile insets take over

Tests: new NavigationMobileMenuPopoverPositionTest exercises a real 404 URL and locks in open-time positioning + the scroll/resize listener pairing. It and the existing NavigationMobileMenuBreakpointTest pass (4 tests, 15 assertions). Pint passes.

🤖 Generated with Claude Code

…n 404 pages

The popover's right offset was computed by an x-bind:style binding that
evaluates during Alpine init, while <body x-cloak> is still display:none
(x-cloak removal is deferred to a microtask). Every measured rect reads 0,
yielding right: <viewport - 6>px, and since the binding's only reactive
dependency is `width`, it never re-evaluated on static pages like the 404
page — leaving the menu entirely off-screen.

Compute both top and right in the existing open-time updater instead,
where layout is measurable, and reposition on scroll/resize while open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simonhamp simonhamp marked this pull request as ready for review July 10, 2026 11:07
… Course"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simonhamp simonhamp merged commit 7377887 into main Jul 10, 2026
2 checks passed
@simonhamp simonhamp deleted the fix-404-popover-offscreen branch July 10, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant