Headless / Menu

NavigationMenu

NavigationMenu is the site-navigation behaviour. It renders a nav landmark — a flat list of links, or a recursive tree with hover- and keyboard-driven submenus.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A single component — not a compound one — with two modes. In flat mode you pass children and it wraps them in a nav with one list item each. In tree mode you pass an items array of NavigationMenuItem objects; items with children render as submenu triggers that open on hover or keyboard activation, with submenus positioned automatically.

Unlike the rest of the Menu family, NavigationMenu is persistent — it is the page's navigation, always on screen, not a popup summoned and dismissed.

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { NavigationMenu } from '@usemotif/headless';

API

NavigationMenu

stable
function NavigationMenu(props: NavigationMenuProps): JSX.Element
itemsreadonly NavigationMenuItem[]

The recursive item tree. When provided, NavigationMenu renders tree mode with submenus.

childrenReactNode

Flat-mode children — a single-level list of links or buttons. Ignored when `items` is set.

currentstring

The id of the active item — gets `aria-current="page"`.

aria-labelstring= "Primary"

Label for the `nav` landmark. Set it when a page has more than one nav.

styleCSSProperties

Inline style for the `nav` element.

example.tsx
interface NavigationMenuItem {
id: string;
label: ReactNode;
href?: string;            // leaf items render as anchors
disabled?: boolean;
children?: readonly NavigationMenuItem[];   // presence makes it a submenu
render?: (info: {
  label: ReactNode;
  isOpen: boolean;
  isCurrent: boolean;
  hasChildren: boolean;
  toggleOpen: () => void;
}) => ReactNode;          // custom render override
}

An item with children becomes a submenu trigger. A leaf item with href renders as an <a>; without one, as a <button>. The render slot replaces either form with your own node.

Keyboard

In tree mode, a submenu trigger opens on Arrow Right (or Arrow Down at the top level) and on Enter or Space. Arrow Left and Escape close an open submenu. Focus leaving a submenu's subtree closes it.

Accessibility

NavigationMenu renders a nav landmark — assistive technology lists it among the page's regions, and the aria-label names it. Give every nav on a page a distinct label, so "Primary" and "Footer" are told apart.

The current item gets aria-current="page", marking the user's location in the site. In tree mode, submenu triggers carry aria-haspopup and aria-expanded; the top-level list is a menubar, submenus are menu.

Examples

Flat mode — a simple top nav:

example.tsx
<NavigationMenu aria-label="Primary" current="docs">
<Link id="home" href="/">Home</Link>
<Link id="docs" href="/docs">Docs</Link>
<Link id="blog" href="/blog">Blog</Link>
</NavigationMenu>

Tree mode — a nav with a submenu:

example.tsx
<NavigationMenu
aria-label="Primary"
current="web"
items={[
  { id: 'home', label: 'Home', href: '/' },
  {
    id: 'platform',
    label: 'Platform',
    children: [
      { id: 'web', label: 'Web', href: '/web' },
      { id: 'native', label: 'Native', href: '/native' },
    ],
  },
  { id: 'pricing', label: 'Pricing', href: '/pricing' },
]}
/>