Components

NavigationMenu

Headless navigation disclosure primitives with trigger-driven panels, indicator geometry, and a shared viewport.

When to Use

Use NavigationMenu for website navigation where a top-level destination can open a panel of related links. Use NavList for a simple visible list of links, Menubar for application commands such as File and Edit, and Menu for a temporary action list. NavigationMenu keeps normal link and Tab behavior; it is not a menu-role widget.

Features

  • Supports root and nested navigation menu scopes.
  • Supports delayed open, skip delay, pointer open, click open, and keyboard open.
  • Provides a shared viewport that adapts to the active content size.
  • Provides indicator geometry CSS variables for styling arrows or active markers.
  • Supports active links and aria-current.
  • Supports horizontal and vertical orientation.

Import

import { NavigationMenu } from "@flowstack-ui/atom";

Anatomy

<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger />
      <NavigationMenu.Content />
    </NavigationMenu.Item>
    <NavigationMenu.Item>
      <NavigationMenu.Link />
    </NavigationMenu.Item>
  </NavigationMenu.List>
  <NavigationMenu.Indicator />
  <NavigationMenu.Viewport />
  <NavigationMenu.Sub>
    <NavigationMenu.List>
      <NavigationMenu.Item>
        <NavigationMenu.Trigger />
        <NavigationMenu.Content />
      </NavigationMenu.Item>
    </NavigationMenu.List>
    <NavigationMenu.Viewport />
  </NavigationMenu.Sub>
</NavigationMenu.Root>

API Reference

Each part that renders DOM emits a default [data-slot]. Pass data-slot to override that value for app-specific styling or test selectors.

Root

Renders the nav landmark, owns the active panel, and coordinates opening delays, direction, orientation, and top-level keyboard navigation.

PropTypeDefault
childrenReactNoderequired
valuestring | null-
defaultValuestring-
onValueChange(value: string | null) => void-
delayDurationnumber200
skipDelayDurationnumber300
loopbooleantrue
orientation"horizontal" | "vertical""horizontal"
dir"ltr" | "rtl"Direction.Provider
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-label"Main" by default; native aria-label overrides it
Data attributeValues
[data-slot]"navigation-menu"
[data-orientation]"horizontal" | "vertical"

List

Renders the item list.

PropTypeDefault
childrenReactNoderequired
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"navigation-menu-list"
[data-orientation]"horizontal" | "vertical"

Item

Provides a value scope for a trigger/content pair or link.

PropTypeDefault
childrenReactNoderequired
valuestringrequired
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"navigation-menu-item"

Trigger

Renders the button that controls its item's panel and participates in roving top-level keyboard navigation.

PropTypeDefault
childrenReactNoderequired
disabledbooleanfalse
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-expandedWhether this item's content is open
aria-controlsGenerated content ID
aria-disabledPresent when disabled
Data attributeValues
[data-slot]"navigation-menu-trigger"
[data-state]"open" | "closed"
[data-disabled]Present when disabled

Content

Registers panel content for the shared viewport.

Content does not render at its declaration site. Its asChild and render customize the content wrapper rendered by Viewport.

PropTypeDefault
childrenReactNoderequired
asChildbooleanfalse
loopbooleanRoot value
renderRenderProp-
Data attributeValues
[data-slot]"navigation-menu-content"
[data-state]"open"
[data-motion]"from-start" | "from-end"

Renders a navigation link.

PropTypeDefault
childrenReactNoderequired
activebooleanfalse
hrefstring-
onSelect() => void-
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-current"page" when active
Data attributeValues
[data-slot]"navigation-menu-link"
[data-active]Present when active

Indicator

Renders an optional active trigger indicator.

PropTypeDefault
childrenReactNode-
forceMountbooleanfalse
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-hiddentrue
Data attributeValues
[data-slot]"navigation-menu-indicator"
[data-state]"visible" | "hidden"
[data-orientation]"horizontal" | "vertical"
CSS variableDescription
--atom-navigation-menu-trigger-leftActive trigger left offset
--atom-navigation-menu-trigger-topActive trigger top offset
--atom-navigation-menu-trigger-widthActive trigger width
--atom-navigation-menu-trigger-heightActive trigger height
--atom-navigation-menu-trigger-center-xActive trigger horizontal center
--atom-navigation-menu-trigger-center-yActive trigger vertical center

Viewport

Renders the active content panel.

PropTypeDefault
childrenReactNode-
forceMountbooleanfalse
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"navigation-menu-viewport"
[data-state]"open" | "closed"
[data-orientation]"horizontal" | "vertical"
CSS variableDescription
--atom-navigation-menu-viewport-widthActive content width
--atom-navigation-menu-viewport-heightActive content height

Sub

Creates a nested navigation menu scope.

PropTypeDefault
childrenReactNoderequired
valuestring | null-
defaultValuestring-
onValueChange(value: string | null) => void-
orientation"horizontal" | "vertical"Parent menu orientation
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"navigation-menu-sub"
[data-orientation]"horizontal" | "vertical"

Advanced compound components can use useNavigationMenuContext and useNavigationMenuItemContext; the matching providers and context value types are also public exports.

Examples

Shared Viewport

import { NavigationMenu } from "@flowstack-ui/atom";

export function PrimaryNavigation() {
  return (
    <NavigationMenu.Root aria-label="Primary navigation">
      <NavigationMenu.List>
        <NavigationMenu.Item value="products">
          <NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <NavigationMenu.Link href="/products/analytics">
              Analytics
            </NavigationMenu.Link>
            <NavigationMenu.Link href="/products/reports">
              Reports
            </NavigationMenu.Link>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
      </NavigationMenu.List>
      <NavigationMenu.Indicator />
      <NavigationMenu.Viewport />
    </NavigationMenu.Root>
  );
}
import { NavigationMenu } from "@flowstack-ui/atom";

export function DocumentationNavigation() {
  return (
    <NavigationMenu.Root aria-label="Documentation">
      <NavigationMenu.List>
        <NavigationMenu.Item value="docs">
          <NavigationMenu.Link href="/docs" active>
            Docs
          </NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
    </NavigationMenu.Root>
  );
}

The package also exports getNavigationMenuGeometry, getNavigationMenuGeometryStyle, and getNavigationMenuViewportSizeStyle for consumers that need the same indicator or viewport measurements outside the default parts.

Accessibility

Root renders a nav landmark with an accessible name. Triggers expose expanded state and controlled content IDs. Links use native anchor semantics and aria-current="page" when active. Text direction can be set with dir on Root or inherited from Direction.Provider.

NavigationMenu follows the WAI-ARIA APG disclosure navigation example for site navigation. It does not use menu, menubar, or menuitem roles, and it does not trap focus. Tab and Shift+Tab remain the primary way to move through visible buttons and links. Arrow keys supplement normal tab navigation.

Trigger Keys

KeyDescription
Enter / SpaceOpens or closes a trigger and keeps focus on the trigger
ArrowDown / ArrowUpIn horizontal orientation, opens content and moves focus to the first or last focusable content element
ArrowRight / ArrowLeftIn horizontal orientation, moves between top-level controls, mirrored in RTL
ArrowDown / ArrowUpIn vertical orientation, moves between top-level controls
ArrowRightIn vertical LTR, opens content and moves focus to the first focusable content element
ArrowLeftIn vertical RTL, opens content and moves focus to the first focusable content element
Home / EndMoves to the first or last top-level control
EscapeCloses the active panel and restores focus to its trigger

Top-level arrow navigation includes both disclosure triggers and direct top-level links. When a panel is open, moving to another trigger switches the open panel. Moving to a direct link closes the open panel without activating the link.

Content Keys

KeyDescription
Tab / Shift+TabMoves through visible focusable content and then returns to the top-level navigation order
ArrowDown / ArrowUpMoves to the next or previous focusable content element in DOM order
Home / EndMoves to the first or last focusable content element
EscapeCloses the active panel and restores focus to its trigger

Content arrow navigation uses Root loop by default. Set loop={false} on Content to stop ArrowUp and ArrowDown at the first or last focusable content element without changing top-level Trigger/Link wrapping.

When focus leaves the navigation region, the active panel closes. Nested navigation menu scopes close from the inside out: Escape first closes the innermost sub menu and restores focus to its trigger; a later Escape can close the parent panel.

Changelog

See CHANGELOG.md.