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
The behavior Atom owns before your product adds appearance.
- Supports root and nested navigation menu scopes.
- Supports mouse-hover delay, click/tap, and keyboard open. Touch/pen never start hover timers.
- Provides a shared viewport that adapts to the active content size.
- Centers horizontal viewport content on the active trigger and shifts it back
inside the visible browser viewport when it would collide with an edge.
- Provides indicator geometry CSS variables for styling arrows or active markers.
- Supports active links and
aria-current.
- Supports horizontal and vertical orientation.
Import
Anatomy
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.
children | ReactNode | required |
value | string | null | - |
defaultValue | string | - |
onValueChange | (value: string | null) => void | - |
delayDuration | number | 200 |
skipDelayDuration | number | 300 |
loop | boolean | true |
orientation | "horizontal" | "vertical" | "horizontal" |
dir | "ltr" | "rtl" | Direction.Provider |
asChild | boolean | false |
render | RenderProp | - |
aria-label | "Main" by default; native aria-label overrides it |
[data-slot] | "navigation-menu" |
[data-orientation] | "horizontal" | "vertical" |
List
Renders the item list.
children | ReactNode | required |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "navigation-menu-list" |
[data-orientation] | "horizontal" | "vertical" |
Item
Provides a value scope for a trigger/content pair or link.
children | ReactNode | required |
value | string | required |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "navigation-menu-item" |
Trigger
Renders the button that controls its item's panel and participates in roving
top-level keyboard navigation.
children | ReactNode | required |
disabled | boolean | false |
asChild | boolean | false |
render | RenderProp | - |
aria-expanded | Whether this item's content is open |
aria-controls | Generated content ID |
aria-disabled | Present when disabled |
[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.
children | ReactNode | required |
asChild | boolean | false |
loop | boolean | Root value |
render | RenderProp | - |
[data-slot] | "navigation-menu-content" |
[data-state] | "open" |
[data-motion] | "from-start" | "from-end" |
Link
Renders a navigation link.
children | ReactNode | required |
active | boolean | false |
href | string | - |
onSelect | () => void | - |
asChild | boolean | false |
render | RenderProp | - |
aria-current | "page" when active |
[data-slot] | "navigation-menu-link" |
[data-active] | Present when active |
Indicator
Renders an optional active trigger indicator.
children | ReactNode | - |
forceMount | boolean | false |
collisionPadding | number | 8 |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "navigation-menu-indicator" |
[data-state] | "visible" | "hidden" |
[data-orientation] | "horizontal" | "vertical" |
--atom-navigation-menu-trigger-left | Active trigger left offset |
--atom-navigation-menu-trigger-top | Active trigger top offset |
--atom-navigation-menu-trigger-width | Active trigger width |
--atom-navigation-menu-trigger-height | Active trigger height |
--atom-navigation-menu-trigger-center-x | Active trigger horizontal center |
--atom-navigation-menu-trigger-center-y | Active trigger vertical center |
Viewport
Renders the active content panel.
children | ReactNode | - |
forceMount | boolean | false |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "navigation-menu-viewport" |
[data-state] | "open" | "closed" |
[data-orientation] | "horizontal" | "vertical" |
--atom-navigation-menu-viewport-width | Active content width |
--atom-navigation-menu-viewport-height | Active content height |
--atom-navigation-menu-viewport-left | Collision-resolved Root-relative left offset |
--atom-navigation-menu-viewport-available-width | Width available inside the visible viewport and collision padding |
--atom-navigation-menu-trigger-left | Active trigger left offset |
--atom-navigation-menu-trigger-top | Active trigger top offset |
--atom-navigation-menu-trigger-width | Active trigger width |
--atom-navigation-menu-trigger-height | Active trigger height |
--atom-navigation-menu-trigger-center-x | Active trigger horizontal center |
--atom-navigation-menu-trigger-center-y | Active trigger vertical center |
Sub
Creates a nested navigation menu scope.
children | ReactNode | required |
value | string | null | - |
defaultValue | string | - |
onValueChange | (value: string | null) => void | - |
orientation | "horizontal" | "vertical" | Parent menu orientation |
asChild | boolean | false |
render | RenderProp | - |
[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
Active Link
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.
Pointer hover timing is restricted to mouse input. Touch and pen use the same
click disclosure path as other directly activated controls. Responsive
replacement with a Drawer is an application/styled-layer decision; Atom does
not change this native disclosure navigation into command-menu semantics.
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
Enter / Space | Opens or closes a trigger and keeps focus on the trigger |
ArrowDown / ArrowUp | In horizontal orientation, opens content and moves focus to the first or last focusable content element |
ArrowRight / ArrowLeft | In horizontal orientation, moves between top-level controls, mirrored in RTL |
ArrowDown / ArrowUp | In vertical orientation, moves between top-level controls |
ArrowRight | In vertical LTR, opens content and moves focus to the first focusable content element |
ArrowLeft | In vertical RTL, opens content and moves focus to the first focusable content element |
Home / End | Moves to the first or last top-level control |
Escape | Closes 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
Tab / Shift+Tab | Moves through visible focusable content and then returns to the top-level navigation order |
ArrowDown / ArrowUp | Moves to the next or previous focusable content element in DOM order |
Home / End | Moves to the first or last focusable content element |
Escape | Closes 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
0.21.0
- Added collision-aware, active-trigger-centered horizontal Viewport geometry.
Viewport now exposes its resolved Root-relative inline position and available
visible width, follows visual-viewport resize and zoom movement, and accepts
collisionPadding without requiring a styled layer to remeasure the panel.
0.20.12
- Added public Agent Knowledge for component selection, required composition,
recurring mistakes, and validation.
0.20.11
- Kept a newly hover-opened Trigger open through the click generated by the
same pointer activation, so switching directly between open items does not
close the destination Trigger in Firefox or WebKit.
0.20.10
- Exposed the active trigger's measured geometry variables directly on
NavigationMenu.Viewport, allowing styled vertical menus to keep a shared
Viewport aligned with later triggers without repeating Atom measurement.
Geometry remains Root-relative when consumers place Viewport inside an
authored positioning wrapper.
0.12.0
- Restricted delayed hover open/close timers to mouse input so touch and pen
use only the existing click/tap disclosure path.
- Requalified native navigation semantics, orientation, RTL, direct links,
viewport geometry, indicator geometry, nested scopes, and focus-out close.
0.2.0
- Fixed
NavigationMenu.Indicator and NavigationMenu.Sub asChild
rendering so slotted children keep their own contents.
- Split
NavigationMenu looping so Root controls top-level Trigger/Link
wrapping and Content can override content arrow-key wrapping.
- Completed disclosure-navigation keyboard behavior for
NavigationMenu,
including direct top-level links, vertical orientation, content arrow
navigation, focus-out closing, and nested Escape focus restoration.
- Added
loop to control content arrow-key wrapping and made content arrows
follow focusable DOM order.
- Added standard
asChild/render customization support to the remaining
NavigationMenu parts, including viewport-rendered content.
- Fixed
data-slot override support across NavigationMenu parts,
including viewport-rendered content.
- Added horizontal trigger roving keyboard navigation for
NavigationMenu,
including RTL-mirrored ArrowLeft and ArrowRight handling.
- Added
Direction.Provider fallback for NavigationMenu.Root direction.
- Added shared dismissable layer Escape handling so NavigationMenu panels close
as the topmost active layer when nested with other overlays.
- Refined trigger, indicator, and viewport callback dependencies to avoid recreating callbacks from the full context object.
0.1.0