Experience System
Components

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-system

In 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
└── NavigationMenuIndicator

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).

PropTypeDefault
viewportbooleantrue (experience system)
defaultValuestringNo default value
valuestringNo default value
onValueChangefunctionNo default value
delayDurationnumber200
skipDelayDurationnumber300
direnumNo default value (inherit from document or ThemeProvider)
orientationenum"horizontal"
Data attributeValues
[data-orientation]"vertical" | "horizontal"

List

NavigationMenuList — top-level items container. Forwards NavigationMenu.List props.

PropTypeDefault
asChildbooleanfalse
Data attributeValues
[data-orientation]"vertical" | "horizontal"

Item

NavigationMenuItem — one top-level slot (trigger + content, or a link). Forwards NavigationMenu.Item props.

PropTypeDefault
asChildbooleanfalse
valuestringNo default value

Trigger

NavigationMenuTrigger — toggles the paired Content. Forwards NavigationMenu.Trigger props. The Experience System trigger appends a trailing chevron icon.

PropTypeDefault
asChildbooleanfalse
Data attributeValues
[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).

PropTypeDefault
asChildbooleanfalse
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
Data attributeValues
[data-state]"open" | "closed"
[data-motion]"to-start" | "to-end" | "from-start" | "from-end"
[data-orientation]"vertical" | "horizontal"

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.

PropTypeDefault
asChildbooleanfalse
activebooleanfalse
onSelectfunctionNo default value
Data attributeValues
[data-active]Present when active

Indicator

NavigationMenuIndicator — optional marker under the list highlighting the active trigger. Forwards NavigationMenu.Indicator props.

PropTypeDefault
asChildbooleanfalse
forceMountbooleanNo default value
Data attributeValues
[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.

PropTypeDefault
asChildbooleanfalse
forceMountbooleanNo default value
Data attributeValues
[data-state]"open" | "closed"
[data-orientation]"vertical" | "horizontal"
CSS variableDescription
--radix-navigation-menu-viewport-widthViewport width derived from active content
--radix-navigation-menu-viewport-heightViewport 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

KeyDescription
Space / EnterWhen focus is on Trigger, opens the content.
TabMoves focus to the next focusable element.
ArrowDownWhen orientation is horizontal and focus is on an open Trigger, moves focus into Content; otherwise moves focus to the next Trigger or Link.
ArrowUpMoves focus to the previous Trigger or Link.
ArrowRight / ArrowLeftWhen 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 / EndMoves focus to the first or last Trigger or Link.
EscCloses open Content and returns focus to its Trigger.

For the full upstream API, behavior, and animation variables, see Radix UI — Navigation Menu.