Skip to content

fix(navigation-menu): open the dropdown panel under its trigger - #222

Merged
ndlabdev merged 2 commits into
devfrom
fix/220-navigation-menu-viewport-align
Sep 18, 2026
Merged

ndlabdev merged 2 commits into
devfrom
fix/220-navigation-menu-viewport-align

Conversation

@ndlabdev

Copy link
Copy Markdown
Owner

Summary

A horizontal NavigationMenu rendered every dropdown in one shared panel pinned to the menu's left edge. A trigger near the end of the menu opened a panel far away from it: 442px off for the last of six triggers, and fully detached (up to 202px apart) when the dropdown content was narrow.

The panel now opens under the trigger that opened it, centred by default, and stays inside the menu when the trigger sits near an edge. A new align prop chooses start, center or end.

The list also reserved 8px under its items for the overlay scrollbar at all times. That room is now added only while the list overflows.

Closes #220

Type of change

  • 🐛 Bug fix
  • ✨ New feature / component
  • 📖 Documentation
  • ♻️ Refactor / chore
  • ⚠️ Breaking change

Changes

  • NavigationMenu: measures the open trigger's inline start and width and exposes them as --sv5ui-nav-trigger-start and --sv5ui-nav-trigger-width. Positioning stays in CSS: one clamp() on inset-inline-start combines the trigger, the panel width the viewport already exposes, and an alignment factor, so the panel never leaves the menu. Only the trigger is measured in JS.
  • The position is recomputed when the open item changes, when the menu resizes and when the list scrolls horizontally. It uses the existing resize observers and respects dir="rtl".
  • The panel slides between triggers. When the menu opens from closed it appears in place instead of sliding in from the left, including with unmountOnHide={false}.
  • Below the sm breakpoint the panel keeps its full width, as before.
  • New align prop ('start' | 'center' | 'end', default 'center'), implemented as a variant.
  • The scroll slot uses data-[overflow]:pb-2 instead of a permanent pb-2. Overflow is detected in the existing resize observers, so a menu that fits no longer shows the gap, and an overflowing menu still keeps the scrollbar clear of the items. Hovering does not change the height.
  • Docs: a Dropdown Alignment section on /navigation-menu switches align live.
  • 5 regression tests, written in the existing spec without fixture files: centred by default, start, end, panel kept inside the menu at an edge, and the scroll padding toggling as the menu shrinks and grows. The alignment tests and the padding test fail on dev; the edge test passes on both and guards the clamp.
  • CHANGELOG.md entries under [Unreleased].

Checklist

  • Linked the related issue (Closes #…)
  • pnpm check passes (0 errors, 0 warnings)
  • pnpm lint passes
  • pnpm test passes
  • Added or updated tests for the change
  • Updated CHANGELOG.md under [Unreleased]
  • Followed component conventions (no comments outside *.types.ts, Material 3 design tokens)

Screenshots / notes

Verified in Chromium against dev:

  • Six triggers in three layouts (full width with wide content, full width with narrow content, inside Header): the panel sits under every trigger and inside the menu. With room it is centred to 0px; the first and last triggers are clamped to the menu edges.
  • align start, center and end land at 0px in both LTR and RTL.
  • Scrolling the list by 60px moves the panel by 60px. At 500px wide the panel keeps its full width.
  • The 12 horizontal triggers on /navigation-menu: panel sizes and the gap to the trigger are unchanged; wide mega panels stay where they were, two move closer to their trigger. The arrow indicator keeps its position and now sits over the panel.
  • Scroll padding: 0px and a 32px menu when it fits, 8px when it overflows with the scrollbar overlapping the items by 0px, toggling live on resize. The dropdown now opens 6px under the trigger instead of 14px.
  • No console errors.

Behaviour change: every horizontal menu now centres its panel on the trigger. A menu that relied on the 8px gap under the list is 8px shorter.

Only verified in Chromium.

A horizontal NavigationMenu rendered every dropdown in one shared panel
pinned to the menu's left edge, so a trigger near the end opened a panel
hundreds of pixels away, detached from it when the content was narrow.

The panel now follows the open trigger. The component measures the
trigger's inline start and width; CSS places the panel with clamp() so it
stays inside the menu, using the panel width the viewport already
exposes. It slides between triggers, appears in place when the menu
first opens, follows the list when it scrolls, and respects dir="rtl".
Below the sm breakpoint the panel keeps its full width.

A new align prop ('start' | 'center' | 'end', default 'center') chooses
where the panel sits relative to the trigger. The docs page gains a
Dropdown Alignment section to switch it live.

The list also reserved 8px under its items for the overlay scrollbar at
all times. That room is now added only while the list overflows, so a
menu that fits no longer shows the gap.

Closes #220
@ndlabdev ndlabdev added the bug Something isn't working label Sep 17, 2026
@ndlabdev ndlabdev self-assigned this Sep 17, 2026
@ndlabdev
ndlabdev merged commit 8abe758 into dev Sep 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant