Navigation Menu
A list of links to a site’s sections, laid out in a row or a column, with at most one nested level. It is the list only: a header bar, an action rail or a collapsible side panel around it is site chrome.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<NavigationMenu
label="Docs"
items={[
{ label: 'Overview', href: '#overview', current: true },
{ label: 'Foundations', href: '#foundations' },
{ label: 'Components', href: '#components' },
]}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/navigation-menu.jsonimport { NavigationMenu } from '@/components/ui/navigation-menu'
import type { NavItem, NavChild } from '@/components/ui/navigation-menu'Anatomy
Top-level items, and nested items under any of them. The type stops there: a nested item has no items of its own, so a third level cannot be written.
- Top-level itemsRequired
- Nested items per top-level itemOptional
Variants
Orientation is the one prop. Horizontal wraps onto a new row when it runs out of room rather than scrolling; vertical is what a sidebar uses. Nested items render inline, open, in both.
States
Focus is forced on the first link with the declarations :focus-visible carries. There is no Hover row because links have no hover style. Current is data, not a pseudo-class: the caller sets it on one item.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| orientation | 'horizontal' | 'vertical' | — |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
primaryActive/current-page indicator.on-surfaceDefault items.spacingContained list padding and gaps between levels.radiusCorner on each item.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Mark one item current per menu, and let the inset bar show it. It is a shape as well as a colour.
- Use horizontal where every item fits on one row, and vertical for a short flat list. A nested sidebar is a Tree view.
- Pass a distinct label to each menu when a page has more than one.
- Keep nested items to the pages of their parent section, and few enough to leave the list open.
Don’t
- Nest a third level. The contract says it becomes its own page, and the type cannot express it.
- Fold a header bar, an action rail or a collapsible panel into it. That is site chrome, and it lives outside components/ui.
- Replace the inset bar with a colour change alone. Colour never carries meaning on its own here.
- Expect nested items to open as a flyout. That waits on the Wave 5 overlay surface; today they sit inline.
Accessibility
What the component does for you, and what it leaves to you.
- Landmark
- A nav landmark named by label, which defaults to “Main”. Give each menu on a page its own name.
- Current page
- The current item carries aria-current="page", and a 2px inset bar in primary as well as the colour change.
- Keyboard
- Plain links in lists. Tab moves through them in order, nested items included; there is no arrow-key handling to learn.
- Focus
- A 2px
--graphite-focusoutline inside the link, on :focus-visible only. - Structure
- Nested items are a list inside their parent’s list item, so the hierarchy a screen reader announces is the one on screen.
