abaabil

Navigation menu

Site navigation: a <nav> around a list of links, where an entry with children is a <button popovertarget> and a popover panel of more links. The browser opens, closes and positions the panels; nothing here measures the DOM.

Example (a11y tier)

Click Products, or focus it and press ArrowDown. Tab walks the links in the panel; ArrowUp or Escape closes it and returns focus to the trigger. Every link here points at #, so nothing navigates away.

Import

import NavigationMenu from 'abaabil/navigation-menu'         // structure only, no ARIA
import NavigationMenu from 'abaabil/navigation-menu/styled'  // + CSS
import NavigationMenu from 'abaabil/navigation-menu/a11y'    // + nav name, aria-current, aria-expanded, keyboard

Navigation, not a menu

No tier sets role="menu" or role="menuitem". The panels hold links, and the APG calls a set of links behind a button a disclosure navigation. A screen reader told the panel is a menu widget would try to operate it as one, with arrow keys and a single tab stop, and the links would leave the page's normal reading order for nothing in return.

The same reasoning removes aria-haspopup, whose default value is menu: exactly the widget this is not. For a list of actions, use menu.

What the browser does, and what is left

The Popover API supplies the top layer, opening on click, closing on an outside click or Escape, and one panel open at a time, because opening an auto popover closes its siblings. CSS anchor positioning puts each panel under its trigger, with fallbacks that flip it when it would leave the viewport. Where anchor positioning is missing, the browser centres the panel instead; nothing is measured in JavaScript to compensate.

The a11y tier adds what the browser cannot say on its own: a name for the <nav>, aria-current="page" on the current link, and a live aria-expanded on every trigger, read from the panels' own toggle events rather than a second copy of the open state, since the browser can close a panel without telling the component.

Keyboard, and the id

Tab walks links and triggers in order, and inside an open panel, its links. ArrowDown on a trigger opens the panel and focuses its first link; ArrowUp or Escape inside a panel closes it and returns focus to the trigger. That is the whole model. There is no roving tabindex and no arrow movement along the top row: these are links, and the APG reserves that behaviour for menubars of actions.

openOnHover is off by default. When on, a panel also opens after the pointer rests on its trigger for 150ms, for mouse and pen only. On touch, pointerenter and click arrive together, and a timer firing after the tap would reopen what the tap just closed. Click still works either way.

id is required rather than generated, as in popover and menu: each panel's id is ${id}-${index}, and generating one would mean useId and a client boundary for the two tiers that otherwise render on the server.

Props

PropTypeDefaultTierDescription
idstring— (required)allPanel ids are ${id}-${index}.
itemsArray<{ key?, label, href?, current?, items?: Array<{ key?, label, href, description? }> }>—allAn entry with items renders a trigger and a panel; otherwise a link. current is applied as data-current.
classNamestring—allMerged with the base class.
labelstring—a11yNames the <nav>. Warns in development without one, since a page with several navigation landmarks announces each as “navigation”.
openOnHoverbooleanfalsea11yAlso opens a panel after the pointer rests on its trigger for 150ms, mouse and pen only. Click still works.