Navigation Menu
A collection of links for site-wide navigation, with optional mega-menu panels per section.
A horizontal navigation menu. Each top-level NavigationMenuItem can open a NavigationMenuContent panel (often a list of NavigationMenuLink rows) or act as a plain link using NavigationMenuLink with className matching NavigationMenuTrigger chrome (see NavigationMenu/variants.ts in the repo, or the design-site demo helper navigationMenuTriggerChromeClassName) so it visually matches triggers.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Usage
import {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuTrigger,
NavigationMenuContent,
NavigationMenuLink,
} from '@by/experience-system';NavigationMenu and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.
<NavigationMenu viewport={false}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Section</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink asChild>
<a href="/path">Link</a>
</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>Wrap the tree in ThemeProvider so dir matches your app (or pass dir explicitly on NavigationMenu).
When to use
- Primary site or product navigation with a small set of sections and optional dropdown-style panels.
- Mega menus where each trigger reveals a structured panel (links, short descriptions) while keeping one tab stop per top-level item.
When not to use
- Command menus or dense action lists—use Dropdown Menu or Menubar instead.
- Mobile-first navigation that should collapse into a drawer or sheet—compose Drawer / Sheet patterns instead of a wide horizontal menu alone.
Composition
Use this structure to build a navigation menu (optional NavigationMenuIndicator at the end of the list; NavigationMenuViewport is rendered by NavigationMenu when viewport is true):
NavigationMenu
├── NavigationMenuList
│ ├── NavigationMenuItem
│ │ ├── NavigationMenuTrigger
│ │ └── NavigationMenuContent
│ │ ├── NavigationMenuLink
│ │ └── NavigationMenuLink
│ └── NavigationMenuItem
│ └── NavigationMenuLink
└── NavigationMenuIndicatorLink component
Use asChild on NavigationMenuLink so the link merges props into your own element Link. When the item is a direct navigation target but should look like a trigger, set className to the same utilities NavigationMenuTrigger uses (copy from packages/experience-system/src/components/NavigationMenu/variants.ts, or reuse the navigationMenuTriggerChromeClassName helper from the design-site navigation-menu demos).
import Link from 'next/link';
import { NavigationMenuItem, NavigationMenuLink, cn } from '@by/experience-system';
const navigationMenuTopLevelLinkClassName = cn(`
group/navigation-menu-trigger inline-flex h-scaled-8 w-max items-center justify-center
rounded-sm px-scaled-4 py-scaled-1.5 text-base font-normal text-neutral-12 transition-all
outline-none
hover:bg-neutral-alpha-3
focus:bg-neutral-alpha-4
focus-visible:ring-2 focus-visible:ring-accent-alpha-9 focus-visible:ring-offset-2
focus-visible:ring-offset-accent-3
disabled:pointer-events-none disabled:opacity-50
data-[state=open]:bg-neutral-alpha-3
data-[state=open]:hover:bg-neutral-alpha-3
data-[state=open]:focus:bg-neutral-alpha-3
[&_svg]:-me-scaled-2 [&_svg]:size-4
`);
export function NavigationMenuDocumentationLink() {
return (
<NavigationMenuItem>
<NavigationMenuLink asChild className={navigationMenuTopLevelLinkClassName}>
<Link href="/system/overview/installation">Documentation</Link>
</NavigationMenuLink>
</NavigationMenuItem>
);
}Examples
Overview
Two trigger-based sections, a top-level link styled as a trigger, and NavigationMenuIndicator. The dir property follows ThemeProvider to ensure correct LTR/RTL direction.
API Reference
Pieces follow Radix UI Navigation Menu (radix-ui). @by/experience-system exports NavigationMenu (Root), NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, and NavigationMenuViewport. Styling uses design tokens (neutral, functional-card-background, scaled spacing). Implementation: packages/experience-system/src/components/NavigationMenu/NavigationMenu.tsx and variants.ts. Agent-oriented contracts: NavigationMenu.instructions.md.
Root
NavigationMenu forwards NavigationMenu.Root props and adds viewport: when true (default), the root renders NavigationMenuViewport after children; when false, content is positioned per-panel (see Radix Flexible layouts).
| Prop | Type | Default |
|---|---|---|
viewport | boolean | true (experience system) |
defaultValue | string | No default value |
value | string | No default value |
onValueChange | function | No default value |
delayDuration | number | 200 |
skipDelayDuration | number | 300 |
dir | enum | No default value (inherit from document or ThemeProvider) |
orientation | enum | "horizontal" |
| Data attribute | Values |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
List
NavigationMenuList — top-level items container. Forwards NavigationMenu.List props.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Data attribute | Values |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
Item
NavigationMenuItem — one top-level slot (trigger + content, or a link). Forwards NavigationMenu.Item props.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | No default value |
Trigger
NavigationMenuTrigger — toggles the paired Content. Forwards NavigationMenu.Trigger props. The Experience System trigger appends a trailing chevron icon.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
Content
NavigationMenuContent — panel for the active trigger. Forwards NavigationMenu.Content props. Motion classes respond to data-motion (from-start, from-end, to-start, to-end).
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-motion] | "to-start" | "to-end" | "from-start" | "from-end" |
[data-orientation] | "vertical" | "horizontal" |
Link
NavigationMenuLink — navigational link (top-level or inside Content). Forwards NavigationMenu.Link props. Prefer asChild with <a> or your router’s link, and active (or data-active) for the current route—see Radix With client side routing.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
active | boolean | false |
onSelect | function | No default value |
| Data attribute | Values |
|---|---|
[data-active] | Present when active |
Indicator
NavigationMenuIndicator — optional marker under the list highlighting the active trigger. Forwards NavigationMenu.Indicator props.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
forceMount | boolean | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "visible" | "hidden" |
[data-orientation] | "vertical" | "horizontal" |
Viewport
NavigationMenuViewport — optional region where active Content is shown for viewport-based layouts. In this experience system the primitive is wrapped in a positioned div; forwarded props apply to the Radix viewport. When NavigationMenu uses viewport={true}, a viewport is mounted automatically; the named export is for advanced composition.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
forceMount | boolean | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-orientation] | "vertical" | "horizontal" |
| CSS variable | Description |
|---|---|
--radix-navigation-menu-viewport-width | Viewport width derived from active content |
--radix-navigation-menu-viewport-height | Viewport height derived from active content |
Accessibility
Radix Navigation Menu follows the navigation role requirements and is not a WAI-ARIA menu / menubar surface—see Differences to menubar and the W3C Disclosure Navigation Menu example. Use NavigationMenuLink (not only raw anchors) for links inside the tree so keyboard behavior and active / aria-current stay consistent; see Link usage and aria-current.
Keyboard interactions
| Key | Description |
|---|---|
Space / Enter | When focus is on Trigger, opens the content. |
Tab | Moves focus to the next focusable element. |
ArrowDown | When orientation is horizontal and focus is on an open Trigger, moves focus into Content; otherwise moves focus to the next Trigger or Link. |
ArrowUp | Moves focus to the previous Trigger or Link. |
ArrowRight / ArrowLeft | When orientation is vertical and focus is on an open Trigger, moves focus into Content; otherwise moves focus to the next / previous Trigger or Link. |
Home / End | Moves focus to the first or last Trigger or Link. |
Esc | Closes open Content and returns focus to its Trigger. |
For the full upstream API, behavior, and animation variables, see Radix UI — Navigation Menu.