Skip to content

mega-menu: a wide panel taller than the room below it scrolls inside itself - #26

Merged
ecropolis merged 1 commit into
mainfrom
claude/mega-menu-panel-scroll
Sep 27, 2026
Merged

ecropolis merged 1 commit into
mainfrom
claude/mega-menu-panel-scroll

Conversation

@ecropolis

Copy link
Copy Markdown
Owner

Problem

In wide (non-compact) mode .mm__panel had no max-height and no overflow. Hosts usually put the menu in a sticky header, where the page cannot scroll a tall panel into view, so its bottom links are unreachable. On superherotech.ai the Features panel is about 1,019px tall at 1440px wide (after ecropolis/SuperheroTech#37). On an 800–900px-tall window, Controls & details and "See all elements" are cut off.

Fix

  • .mm__panel gets max-height: var(--mm-panel-max, calc(100dvh - var(--mm-panel-top, 5rem))), plus box-sizing: border-box, overflow-y: auto and overscroll-behavior: contain. Border-box keeps the 1px top rule inside the cap, so the bottom edge stays on screen.
  • The script sets --mm-panel-top on the open panel to where the panel starts.
    • It reads layout offsets (offsetParent rect + clientTop + offsetTop, or offsetTop - scrollY against <body>), not the panel's own box, because the opening animation translates the panel.
    • It measures synchronously in open(), so there is no uncapped first frame. It measures again at most once a frame on scroll and resize while a panel is open, which covers a header that shrinks on scroll.
  • Without JavaScript the :hover / :focus-within reveal is unchanged, and the cap assumes the panel starts 5rem down.
  • The collapsed accordion lifts the cap (max-height: none; overflow: visible), since its list already scrolls as a whole.
  • New theming variable --mm-panel-max replaces the cap: a length such as 70vh, or none to lift it. It is documented in the component header, the catalogue's theming list, and a new a11y line.

Check

New scripts/check-mega-menu.mjs, added to npm run check:

  1. The // <mm-fit> block in the built page must equal the source's, and runs against 5 fake-panel cases. Reading the panel's own box throws.
  2. It pins the panel cap, border-box, overflow and overscroll in both the source and the built CSS. It also checks that the no-JS reveal is still there and that the compact rule lifts the cap.
  3. It checks the script: open() calls fit(), a passive scroll listener and a resize listener re-measure, and nothing is measured while collapsed.
  4. Every --mm-* the stylesheet reads, except the script-set --mm-panel-top, must be in both the header and the catalogue, and vice versa.
  5. 21/21 mutants are killed.

Full npm run check is green locally.

Verified in the gallery

I ran a temporary gallery page (not committed) in headless Chrome over CDP. It had a sticky header, --mm-anchor: static, and a Features panel of seven categories plus "More → See all elements" (scrollHeight 1,272px). The page was scrolled 600px:

Case Result
Hover-open at 1440×820 top 84, bottom 820 (= viewport), max-height 736px, --mm-panel-top: 84px
Wheel over the panel panel scrollTop reaches max (537); last links in view; page scrollY stays 600 even past the end
Resize to 600 tall while open max-height 516px, bottom 600
Header shrinks on scroll --mm-panel-top follows to 63px, bottom still 820
Mouse leaves the item hover-opened panel closes
Keyboard: Enter, ↓, Tab ×N opens capped, ↓ focuses "All features", Tab reaches "See all elements" with the panel scrolled, focus in view, page not scrolled; Escape closes and refocuses the button
Compact at 800×700 panel max-height: none; overflow: visible; the list (80vh) scrolls
JS disabled, hover and Tab from top link panel shows, capped at 740px (5rem stand-in), wheel and Tab both reach "See all elements"

Without JavaScript, the 5rem stand-in is 4px short for this 84px header, so the panel's bottom 4px of padding sit below the fold. The links are still all reachable. A host with a much taller header can set --mm-panel-max for the no-JS case.

🤖 Generated with Claude Code

…itself

Hosts put the menu in a sticky header, where the page cannot scroll a tall panel
into view, so its last links were out of reach (superherotech.ai's Features panel
is ~1,019px at 1440 wide). The open panel is now capped at
var(--mm-panel-max, calc(100dvh - var(--mm-panel-top, 5rem))), border-box, with
overflow-y: auto and overscroll-behavior: contain. The script measures where the
panel starts from layout offsets (the opening animation translates it) on open,
and again on scroll and resize while open. The collapsed accordion lifts the cap;
its list already scrolls as a whole.

--mm-panel-max is documented in the header and the catalogue. check-mega-menu.mjs
runs the shipped panelTop against fake panels, pins the stylesheet (source and
built), the script's re-measuring and the docs, and kills 21 mutants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ecropolis
ecropolis merged commit f685181 into main Sep 27, 2026
2 checks passed
@ecropolis
ecropolis deleted the claude/mega-menu-panel-scroll branch September 27, 2026 18:03
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