# Navigation Menu

An accessible React navigation menu for Next.js and Tailwind CSS: a top bar with rich dropdown panels that glide between sizes, built on Radix Navigation Menu.

Source: https://ui.fewtone.com/components/navigation-menu

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/navigation-menu.json
```

## Usage

```tsx
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"

export function SiteNav() {
  return (
    <NavigationMenu>
      <NavigationMenuList>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Components</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul className="grid w-md grid-cols-2 gap-1">
              <li>
                <NavigationMenuLink href="/components/button">
                  <span className="font-medium">Button</span>
                  <span className="text-muted-foreground">Actions, in calm variants.</span>
                </NavigationMenuLink>
              </li>
              <li>
                <NavigationMenuLink href="/components/dialog">
                  <span className="font-medium">Dialog</span>
                  <span className="text-muted-foreground">A window that holds focus.</span>
                </NavigationMenuLink>
              </li>
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="/docs">Docs</NavigationMenuLink>
        </NavigationMenuItem>
        <NavigationMenuIndicator />
      </NavigationMenuList>
    </NavigationMenu>
  )
}
```

## API reference

### NavigationMenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | The open item, when you control it. An empty string is closed. |
| `defaultValue` | `string` | `""` | The item open at first. |
| `onValueChange` | `(value: string) => void` | – | Called with the item that opens, or an empty string when the menu closes. |
| `viewport` | `boolean` | `true` | Opens the panels in one shared surface under the list, which glides between their sizes. Set it to false to open each panel under its own trigger. |
| `delayDuration` | `number` | `200` | How long the pointer rests on a trigger before its panel opens, in milliseconds. |
| `skipDelayDuration` | `number` | `300` | How soon after leaving one trigger another opens without the delay, in milliseconds. |
| `aria-label` | `string` | `"Main"` | Names the landmark. Give each navigation menu on a page its own name. |

### NavigationMenuList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | – | NavigationMenuItem elements. |

### NavigationMenuItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | Names the item for value and onValueChange. One is made up when you leave it out. |

### NavigationMenuTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Can't be opened or focused. |

### NavigationMenuContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | – | The panel's links and layout. |
| `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | – | Called when Escape closes the menu. Call event.preventDefault() to keep it open. |

### NavigationMenuLink

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | – | Where it goes. |
| `active` | `boolean` | `false` | Marks the current page (aria-current="page"). |
| `asChild` | `boolean` | `false` | Renders your own link instead, for example Next.js's Link. |
| `onSelect` | `(event: Event) => void` | – | Called when the link is selected. Call event.preventDefault() to keep the menu open. |

### NavigationMenuIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | – | Classes for the caret. |

### NavigationMenuViewport

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | – | Classes for the surface. |

### navigationMenuTriggerStyle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `()` | `() => string` | – | Returns the class names. Merge your own with cn. |

**When to use it:** Use Navigation Menu for a website's top bar, where some items open panels of links (a docs or marketing site header). Use Dropdown Menu for a list of actions, Tabs to switch panels in place, Sidebar for an app's navigation, and a Sheet for the menu on small screens.
**Built on:** Radix Navigation Menu.
**Accessibility pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
**npm packages (installed by the CLI):** class-variance-authority@^0.7.1, lucide-react@^1.52.0, motion@^14.0.0, radix-ui@^1.7.0.

## Examples

### Site header

Hover or open a trigger, then move to the other one: the panel glides to its new size, the content slides in from the side you moved towards, and the caret follows the open trigger. One highlight glides between the items.

```tsx
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import { BookOpenIcon } from "lucide-react";
import type { ReactNode } from "react";

const guides = [
  {
    href: "/docs/installation",
    title: "Installation",
    description: "Add components with the shadcn CLI, or copy the source.",
  },
  {
    href: "/docs/installation#customize-the-theme",
    title: "Theming",
    description: "Colours, radius and fonts, set with CSS variables.",
  },
  {
    href: "/roadmap",
    title: "Roadmap",
    description: "What's available now, what's next and what's planned.",
  },
];

const components = [
  { href: "/components/button", title: "Button", description: "Actions, in calm variants." },
  { href: "/components/dialog", title: "Dialog", description: "A window that holds focus." },
  { href: "/components/select", title: "Select", description: "One option from a list." },
  { href: "/components/tabs", title: "Tabs", description: "Panels under a sliding line." },
  { href: "/components/toast", title: "Toast", description: "Brief messages that stack." },
  { href: "/components/tooltip", title: "Tooltip", description: "A short label on hover." },
];

/** A panel row: a title over a muted description */
function ListItem({
  href,
  title,
  children,
  className,
}: {
  href: string;
  title: string;
  children: ReactNode;
  className?: string;
}) {
  return (
    <li className={className}>
      <NavigationMenuLink href={href}>
        <span className="font-medium">{title}</span>
        <span className="leading-snug text-muted-foreground">{children}</span>
      </NavigationMenuLink>
    </li>
  );
}

/*
 * A site header's menu: two triggers with panels of different sizes, and a plain link. Moving between the
 * triggers, the panel glides to the next one's size, its content slides in from the side you moved towards,
 * and the caret follows. The box is as tall as the open panel needs.
 */
export default function NavigationMenuHeader() {
  return (
    <div className="flex h-88 w-full items-start justify-center">
      <NavigationMenu aria-label="Documentation">
        <NavigationMenuList>
          <NavigationMenuItem>
            <NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-md grid-cols-5 gap-1">
                <li className="col-span-2 row-span-3">
                  <NavigationMenuLink
                    href="/docs"
                    // Its own fill hides the gliding highlight, so it deepens itself on hover
                    className="h-full justify-between gap-4 bg-muted p-4 hover:bg-accent"
                  >
                    <BookOpenIcon className="size-(--size-icon-lg)" />
                    <span className="grid gap-1">
                      <span className="text-base font-medium">Introduction</span>
                      <span className="leading-snug text-muted-foreground">
                        Accessible React components for Next.js and Tailwind CSS, made to copy in.
                      </span>
                    </span>
                  </NavigationMenuLink>
                </li>
                {guides.map((guide) => (
                  <ListItem key={guide.href} href={guide.href} title={guide.title} className="col-span-3">
                    {guide.description}
                  </ListItem>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuTrigger>Components</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-lg grid-cols-2 gap-1">
                {components.map((component) => (
                  <ListItem key={component.href} href={component.href} title={component.title}>
                    {component.description}
                  </ListItem>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink href="/docs">Docs</NavigationMenuLink>
          </NavigationMenuItem>
          <NavigationMenuIndicator />
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  );
}
```

### Panels under their triggers

With viewport={false}, each trigger opens its own small surface right under it, like a row of dropdowns, instead of one shared panel.

```tsx
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";

const menus = [
  {
    label: "Guides",
    links: [
      { href: "/docs", title: "Introduction" },
      { href: "/docs/installation", title: "Installation" },
      { href: "/roadmap", title: "Roadmap" },
    ],
  },
  {
    label: "Components",
    links: [
      { href: "/components/button", title: "Button" },
      { href: "/components/dialog", title: "Dialog" },
      { href: "/components/select", title: "Select" },
      { href: "/components", title: "All components" },
    ],
  },
];

/*
 * viewport={false}: each trigger opens its own small surface right under it, like a row of dropdowns. Plain
 * rows of links, spaced like Select's options.
 */
export default function NavigationMenuWithoutViewport() {
  return (
    <div className="flex h-64 w-full items-start justify-center">
      <NavigationMenu viewport={false} aria-label="Documentation">
        <NavigationMenuList>
          {menus.map((menu) => (
            <NavigationMenuItem key={menu.label}>
              <NavigationMenuTrigger>{menu.label}</NavigationMenuTrigger>
              <NavigationMenuContent className="p-1">
                <ul className="grid w-48">
                  {menu.links.map((link) => (
                    <li key={link.href}>
                      <NavigationMenuLink href={link.href} className="px-2 py-1.5">
                        {link.title}
                      </NavigationMenuLink>
                    </li>
                  ))}
                </ul>
              </NavigationMenuContent>
            </NavigationMenuItem>
          ))}
          <NavigationMenuItem>
            <NavigationMenuLink href="/docs">Docs</NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  );
}
```

## Related

- [Dropdown Menu](https://ui.fewtone.com/components/dropdown-menu.md): An accessible React dropdown menu for Next.js with a highlight that glides between items, shortcuts, checkbox and radio items and sub-menus, built on Radix.
- [Tabs](https://ui.fewtone.com/components/tabs.md): Accessible React tabs for Next.js, with an underline or pill that slides to the active tab and panels that glide in, built on Radix Tabs.
- [Breadcrumb](https://ui.fewtone.com/components/breadcrumb.md): An accessible React breadcrumb for Next.js and Tailwind CSS: a trail of links whose levels animate as you move, with custom separators and a collapsed middle.
- [Sidebar](https://ui.fewtone.com/components/sidebar.md): An accessible React sidebar for Next.js and Tailwind CSS: collapses to icons or off canvas, with a gliding active indicator and a sheet on phones.
