Navigation Menu
Primary site navigation — a horizontal bar whose items open dropdown panels of
grouped links that animate and resize to their content. Built on Base UI
NavigationMenu. For application action menus use Menu.
Import
import { NavigationMenu } from "@photon-ai/pho-ui/components/navigation-menu";
Basic
Each NavigationMenu.Item pairs a Trigger with Content; the content renders
into a shared Viewport inside the popup. A plain NavigationMenu.Link makes a
top-level item with no dropdown.
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>
Products
<NavigationMenu.Icon>
<CaretDown />
</NavigationMenu.Icon>
</NavigationMenu.Trigger>
<NavigationMenu.Content>
<NavigationMenu.Link href="/deploy">Deploy</NavigationMenu.Link>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
<NavigationMenu.Portal>
<NavigationMenu.Positioner>
<NavigationMenu.Popup>
<NavigationMenu.Viewport />
</NavigationMenu.Popup>
</NavigationMenu.Positioner>
</NavigationMenu.Portal>
</NavigationMenu.Root>
Anatomy
- NavigationMenu.Root / .List / .Item — the
<nav>bar (<ul>/<li>) and its items - NavigationMenu.Trigger + .Content — a dropdown item’s
<button>and the panel it opens - NavigationMenu.Icon — the caret inside a trigger; rotates 180° while the panel is open
- NavigationMenu.Link — an
<a>link (top-level, or inside a panel’s content) - NavigationMenu.Portal → Positioner → Popup → Viewport — the shared, resizing panel
Props
Every part also accepts className (a plain string) and render — Base UI’s
prop for swapping the element it renders (e.g. a framework <Link> for a
NavigationMenu.Link). The rest forward to the underlying Base UI part.
NavigationMenu.Root (<nav>; holds open state)
- value —
any— controlled value of the open item; non-nullish keeps the menu open - defaultValue —
any(defaultnull) — initially open item when uncontrolled - onValueChange —
(value, details) => void— fires when the open item changes - delay —
number(default50) — hover-open delay in ms - closeDelay —
number(default50) — hover-close delay in ms - orientation —
horizontal·vertical(defaulthorizontal) — arrow-key navigation axis
NavigationMenu.Trigger (<button>) — opens its Content; gets aria-expanded and data-popup-open (which drives the Icon rotation).
NavigationMenu.Content (<div>) — the body for one item, mounted into the shared Viewport.
NavigationMenu.Link (<a>) — forwards href and any anchor attribute; pass render to route through your framework’s link.
NavigationMenu.Positioner (<div>) — positions the popup against the active trigger
- side —
top·right·bottom·left(defaultbottom) — which side of the trigger to place the popup - sideOffset —
number(default8) — gap between trigger and popup (pho default; Base UI’s own default is0) - align —
start·center·end(defaultcenter) — alignment along that side - alignOffset —
number(default0) — shift along the alignment axis - anchor —
Element · ref · () => Element— element to position against; defaults to the active trigger
NavigationMenu.Popup (<nav>) — the floating surface; sized to --popup-width / --popup-height and animated via data-starting-style / data-ending-style.
NavigationMenu.Viewport (<div>) — the single window each item’s Content renders into; the popup resizes to fit it.
Accessibility
- Semantics —
Rootrenders a<nav>landmark (andPopupanother), so give theRootanaria-labelwhen a page has more than one nav. EachTriggeris a<button>carryingaria-expandedfor its panel (mirrored bydata-popup-open), andLinkrenders a real<a>— reachable, focusable, and openable in a new tab like any link. - Keyboard
Tab/Shift+Tabmove across the top-level triggers and links, then into an open panel and onward through the pageArrow Left/Arrow Rightmove focus between adjacent triggers (Arrow Up/Arrow Downwhenorientation="vertical")Enter/Spaceon a focused trigger open or close its panelEscapecloses the open panel and returns focus to its trigger
- Focus — triggers and links show the brand focus ring on keyboard focus
only (
focus-visible). The panel is a disclosure, not a focus trap: tabbing past its last link simply moves on in the page and closes it.
Best practices
- Use a navigation menu for a website’s primary nav with grouped destinations; keep the top level to a handful of items.
- Put the most important links first in each panel and give them a short description so the dropdown earns its space.
- For deep, app-style command surfaces use a
MenubarorMenuinstead — a navigation menu is about going places, not running commands.