Noctis

NavigationMenu

Site navigation with flyout panels. A bar of muted text links, where each trigger reveals an elevated mega-panel — the box morphs to each panel's size while the panels cross-slide in place. The bar reads like Tabs, the panels like Menu.

Flyout panels

Compose a mega-menu from blessed parts: each trigger's NavigationMenu.Content holds a list of links, where every NavigationMenu.Link pairs a NavigationMenu.LinkTitle over a muted NavigationMenu.LinkDescription. Lay them out however the panel calls for — a multi-column grid here, a single column there. Each panel sizes to its own content, so panels of different shapes give the flyout distinct footprints. Move between the triggers: the single elevated popup morphs its width and height to each panel while the outgoing panel slides and fades out and the incoming one slides in — one continuous motion, never a jump, and the bar always opens downward (it never flips above itself). Bar items are text links that lift to a neutral ghost fill (the focus ring is the one accent use); a plain NavigationMenu.Link in an Item is a direct bar link with no flyout.

Nested submenu

A NavigationMenu.Root can be nested inside a NavigationMenu.Content to add a second level. The inner menu gets its own NavigationMenu.Viewport — here pointed to the side so the submenu flies out beside the panel. A trigger nested in a panel reads as a panel row, not a bar pill.

For a mega-panel with its own internal navigation, nest a vertical NavigationMenu.Root and render its NavigationMenu.Viewport with inline — the panels stay inside the layout (no second portal) and slide vertically as you move down the sidebar. The outer flyout's card provides the surface.

The bar collapses badly on touch, so below a breakpoint hide it and drop to a Sheet drawer holding a vertical NavigationMenu (orientation="vertical"), toggled by a hamburger Button with an aria-label. For SEO, set keepMounted on a Content so its links stay in the DOM for crawlers; wire each Link's active from your router (usePathname() === hrefaria-current="page") and pass closeOnClick to dismiss the panel on navigation. For a compact docs bar, set size="sm" on Root; open timing is tuned by delay / closeDelay (Noctis defaults ~150 / ~180ms).

Keyboard

The bar is fully keyboard-operable, following the WAI-ARIA disclosure-navigation pattern (native <nav> + links — never role="menu").

KeyAction
Arrow Left / Arrow RightMove between top-level items in the bar. Mirrored under RTL — Arrow Left moves toward the inline-end.
Home / EndJump to the first / last top-level item.
Enter / SpaceOpen the focused trigger's flyout (or follow a focused link).
Arrow DownOpen the focused trigger's flyout and move focus into its panel.
Tab / Shift + TabMove through the links inside the open panel, then out of the bar.
EscapeClose the open flyout and return focus to its trigger.

Accessibility

  • Disclosure, not menu. Site navigation uses native semantics — <nav><ul>/<li><button aria-expanded aria-controls> for a flyout trigger and <a> for a link. It deliberately does not use role="menu"/menubar, which are for application menus and impose composite focus and typeahead that confuse screen-reader users on site nav.
  • One current per level. The current page's link carries aria-current="page" (set by active); never signal the current location by colour alone — the selected fill pairs with it, and a current section keeps its fill so "you are here" survives a panel close.
  • Disabled. A disabled trigger or link is dimmed, removed from the tab order, and can't be activated (not just aria-disabled).
  • RTL. All geometry is logical, so the bar, the cross-slide, and the panel padding mirror under dir="rtl".
  • Reduced motion. The size-morph, cross-slide, and chevron rotation are all disabled under prefers-reduced-motion: reduce.

Anatomy

Compose a navigation menu from its parts. NavigationMenu.Root owns the open value (controlled via value / onValueChange, or uncontrolled via defaultValue) and carries the bar size, orientation, and hover-intent delay / closeDelay.

  • NavigationMenu.Root — the container, rendering a <nav>. Props: the Base UI NavigationMenu.Root props plus size (md | sm).
  • NavigationMenu.List — the bar of top-level items. Arrow keys and Home/End rove between them.
  • NavigationMenu.Item — one entry. Wrap a Trigger + Content for a flyout, or a single Link for a direct page.
  • NavigationMenu.Trigger — opens an item's flyout; carries a trailing chevron that rotates while open. Mark it current for the persistent section indicator; disabled to dim it.
  • NavigationMenu.Content — the flyout panel for an item, moved into the shared viewport while active. keepMounted keeps its links in the DOM for crawlers.
  • NavigationMenu.Link — a link, in the bar or inside a panel. active marks the current page; current marks the current section in the bar; closeOnClick dismisses the panel on click.
  • NavigationMenu.LinkTitle / NavigationMenu.LinkDescription — the prominent line and the muted line of a two-line panel link.
  • NavigationMenu.Section / NavigationMenu.SectionTitle — a titled column group inside a panel and its heading label.
  • NavigationMenu.Separator — a sharp 1px divider between panel groups.
  • NavigationMenu.Footer — a pinned "view all"/CTA row at a panel's end.
  • NavigationMenu.Viewport — the single, elevated, portaled flyout the active panel teleports into; it morphs to each panel's size and clips the panels as they cross-slide. Render it once inside Root, after the List. backdrop dims the page behind it.

Every part carries a data-slot for host-side styling — pair it with the state attributes (data-popup-open / data-current / data-disabled on a trigger, data-active on the current link, data-activation-direction on the sliding panel, data-size / data-orientation on the root). The flyout is an elevated <Surface elevation="menu"> with data-elevation on the portaled popup, and the panel transitions respect prefers-reduced-motion.

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every navigation bar in that region retunes — e.g. .brand { --noctis-navigation-menu-positioner-duration: 0.5s; } slows the whole flyout's morph and slide. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through. Expand a row for the full type and description.

NavigationMenu.Root

Prop

NavigationMenu.List

Prop

NavigationMenu.Item

Prop

NavigationMenu.Trigger

Prop

NavigationMenu.Content

Prop

NavigationMenu.Link

Prop

NavigationMenu.LinkTitle

Prop

NavigationMenu.LinkDescription

Prop

NavigationMenu.Section

Prop

NavigationMenu.SectionTitle

Prop

NavigationMenu.Separator

Prop

NavigationMenu.Footer

Prop

NavigationMenu.Viewport

Prop

AttributeDescription
data-slotThe root navigation element.
data-openPresent on the popup, positioner, and content while the flyout is open.
data-popup-openPresent on a trigger while its flyout is open — drives the lit look and the chevron rotation.
data-currentPresent on the trigger/bar-link of the section the current page lives under — the persistent "you are here" at the top level (fill survives panel close).
data-disabledPresent on a disabled trigger/bar-link — dims it and suppresses the hover/open fill.
data-sizeThe bar's control density (`md`/`sm`), stamped on the root to re-point the bar item metrics.
data-activePresent on the link that marks the current page.
data-sideThe side of the anchor the popup actually rendered on (`bottom`, `top`, …).
data-activation-directionThe direction the active content slid in from when switching panels (`left`/`right`/`up`/`down`).
data-starting-stylePresent on the popup/content for the first frame after mount — the transition's start state.
data-ending-stylePresent on the popup/content while it transitions out before unmounting.
data-orientationThe orientation of the navigation bar (`horizontal`/`vertical`).