Skip to content

Navigation menu

hc-navmenu is a top-level site navigation whose items can open content panels (a mega-menu). It is a disclosure set: each trigger is a button[aria-expanded] controlling a popover panel anchored under it with CSS Anchor Positioning. Panels open on hover / focus, one at a time, and the links inside stay real <a> — so it works for plain MPA navigation and htmx alike.

import { installNavmenu } from '@hypermedia-components/core';
installNavmenu();

installNavmenu() is idempotent and returns an uninstaller. The zero-config @hypermedia-components/core/behaviors entry auto-installs it and picks up content swapped in by htmx.

A trigger gets aria-haspopup / aria-expanded / aria-controls wired automatically; its panel becomes a popover anchored beneath it.

  • Hover / focus opens a panel after a short intent delay and closes it after a brief grace period (so moving the pointer from the trigger into the panel doesn’t dismiss it). Only one panel is open at a time.
  • Keyboard: / open the panel and move focus to the first / last item inside; Esc closes it and returns focus to the trigger; Tab moves through triggers and panel links normally.
  • Links stay links. The behavior never intercepts clicks on <a> inside a panel — navigation is the browser’s (or htmx’s, if the link carries data-hx-*).

On narrow screens a horizontal mega-menu doesn’t fit. Because the panels are plain markup, the same content degrades well: collapse the bar behind a toggle (e.g. inside an hc-drawer) and let each section render as a stacked hc-collapsible. Switch layouts with a media query — the links and structure are identical, so no duplicate markup is required.

PrimitiveRequired version
HTML popoverChrome 114, Edge 114, Firefox 125, Safari 17
CSS Anchor PositioningChrome 125, Edge 125, Firefox 147, Safari 26

Where Anchor Positioning is missing, installNavmenu() falls back to JS positioning — the panel still opens and dismisses correctly everywhere popover is supported.

  • Wrap the menu in a <nav> with an aria-label; each panel trigger is a real <button> exposing aria-expanded and aria-controls.
  • Panels are popovers, so Esc and outside-click dismissal come from the platform; focus returns to the trigger on Esc.
  • Keep panel contents as real links / buttons in source order so they are reachable by Tab and announced normally.
Token pathPurpose
navmenu.gapGap between top items.
navmenu.trigger-padding-x / -yTrigger / top-link padding.
navmenu.trigger-radiusTrigger / top-link radius.
navmenu.trigger-fgTrigger text color.
navmenu.trigger-hover-bgTrigger hover / open background.
navmenu.panel-bg / -fg / -borderPanel surface.
navmenu.panel-radius / -padding / -min-widthPanel box.
navmenu.panel-offsetGap between trigger and panel.
navmenu.link-padding-x / -y / -radiusPanel link box.
navmenu.link-fg / -hover-bgPanel link colors.
Show the generated CSS variables
--hc-navmenu-gap | -trigger-padding-x | -trigger-padding-y | -trigger-radius
--hc-navmenu-trigger-fg | -trigger-hover-bg
--hc-navmenu-panel-bg | -panel-fg | -panel-border | -panel-radius | -panel-padding | -panel-min-width | -panel-offset
--hc-navmenu-link-padding-x | -link-padding-y | -link-radius | -link-fg | -link-hover-bg
  • Menubar — application menu bar (actions), not site navigation.
  • Menu — a single dropdown action menu.
  • Drawer — a common host for the mobile version of a site nav.