# Navigation Menu

Open site sections from a row of links with dropdown panels.

Navigation menu with two dropdown panels and a link

[Complete TSX file](/examples/navigation-menu/default.tsx)

```tsx
"use client";

import { useRef } from "react";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuPopup,
  NavigationMenuTrigger,
} from "@/siteplane/components/navigation-menu";

export default function Example() {
  const anchor = useRef<HTMLUListElement>(null);

  return (
    <NavigationMenu aria-label="Main navigation">
      <NavigationMenuList ref={anchor}>
        <NavigationMenuItem value="product">
          <NavigationMenuTrigger>Product</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div className="grid w-80 max-w-full gap-1 p-4">
              <NavigationMenuLink
                description="See what is happening across your workspace."
                href="#overview"
              >
                Overview
              </NavigationMenuLink>
              <NavigationMenuLink
                description="Track work from planning to launch."
                href="#projects"
              >
                Projects
              </NavigationMenuLink>
              <NavigationMenuLink
                description="Understand how your work performs."
                href="#analytics"
              >
                Analytics
              </NavigationMenuLink>
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem value="resources">
          <NavigationMenuTrigger>Resources</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div className="grid w-96 max-w-full gap-1 p-4">
              <NavigationMenuLink
                description="Guides and references for every feature."
                href="#documentation"
              >
                Documentation
              </NavigationMenuLink>
              <NavigationMenuLink
                description="The latest improvements and releases."
                href="#changelog"
              >
                Changelog
              </NavigationMenuLink>
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#pricing">Pricing</NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
      <NavigationMenuPopup anchor={anchor} />
    </NavigationMenu>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/navigation-menu
```

With npm: `npx shadcn@4.18.0 add @siteplane/navigation-menu`. [Registry JSON](/registry/navigation-menu.json) lists source files and dependencies.

## Usage

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

function SiteNavigation() {
  const anchor = useRef<HTMLUListElement>(null);

  return (
    <NavigationMenu aria-label="Main navigation">
      <NavigationMenuList ref={anchor}>
        <NavigationMenuItem value="product">
          <NavigationMenuTrigger>Product</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div className="w-80 p-4">
              <NavigationMenuLink
                description="See your workspace at a glance."
                href="#overview"
              >
                Overview
              </NavigationMenuLink>
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#pricing">Pricing</NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
      <NavigationMenuPopup anchor={anchor} />
    </NavigationMenu>
  );
}
```

## Composition

```text
NavigationMenu
├── NavigationMenuList
│   ├── NavigationMenuItem
│   │   ├── NavigationMenuTrigger
│   │   └── NavigationMenuContent
│   │       └── NavigationMenuLink (panel row)
│   └── NavigationMenuItem
│       └── NavigationMenuLink
└── NavigationMenuPopup (shared positioner, popup and viewport)
```

A link inside `NavigationMenuContent` takes the full-width panel row style on its own, so it needs no `popup` flag, and a link beside the triggers keeps the style of the bar. Links rest in the secondary text color, and hover draws a light fill. The link of the current page, marked with `active`, keeps a persistent fill and the full foreground, the same as the current row of a [Sidebar](/docs/components/sidebar/).

[Header Navigation](/docs/patterns/header-navigation/) builds its desktop links from these parts.

## Multi-column panels

Content is plain markup, so a panel can hold several labeled groups. Give the content element a width, and keep it `max-w-full` so it never outgrows the screen. Moving between triggers keeps one popup open: it resizes to the new content while the old and the new panel cross-fade.

Panel with two labeled groups

[Complete TSX file](/examples/navigation-menu/groups.tsx)

```tsx
"use client";

import { useRef } from "react";
import { Badge } from "@/siteplane/components/badge";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuPopup,
  NavigationMenuTrigger,
} from "@/siteplane/components/navigation-menu";

const groups = [
  {
    label: "Workspace",
    links: [
      {
        description: "See what is happening across your workspace.",
        href: "#overview",
        label: "Overview",
      },
      {
        description: "Updates that need your attention.",
        href: "#notifications",
        label: "Notifications",
      },
      {
        description: "Understand how your work performs.",
        href: "#analytics",
        label: "Analytics",
        isNew: true,
      },
    ],
  },
  {
    label: "Reports",
    links: [
      {
        description: "Return to your saved reports.",
        href: "#saved",
        label: "Saved",
      },
      {
        description: "Manage recurring reports.",
        href: "#scheduled",
        label: "Scheduled",
      },
      {
        description: "Explore activity and engagement.",
        href: "#user-reports",
        label: "User reports",
      },
    ],
  },
];

export default function Example() {
  const anchor = useRef<HTMLUListElement>(null);

  return (
    <NavigationMenu aria-label="Product navigation">
      <NavigationMenuList ref={anchor}>
        <NavigationMenuItem value="dashboard">
          <NavigationMenuTrigger>Dashboard</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div className="grid w-[36rem] max-w-full grid-cols-2 gap-6 p-6">
              {groups.map((group) => (
                <div
                  className="grid min-w-0 content-start gap-2"
                  key={group.label}
                >
                  <h3 className="text-xs font-medium text-muted-foreground">
                    {group.label}
                  </h3>
                  <ul className="grid gap-1">
                    {group.links.map((link) => (
                      <li key={link.href}>
                        <NavigationMenuLink
                          description={link.description}
                          href={link.href}
                        >
                          {link.label}
                          {link.isNew ? (
                            <Badge size="sm" variant="accent">
                              New
                            </Badge>
                          ) : null}
                        </NavigationMenuLink>
                      </li>
                    ))}
                  </ul>
                </div>
              ))}
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#projects">Projects</NavigationMenuLink>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#team">Team</NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
      <NavigationMenuPopup anchor={anchor} />
    </NavigationMenu>
  );
}
```

## Controlled value

Pass `value` and `onValueChange` to open and close panels from your own state. Match `value` to the `value` of a `NavigationMenuItem`; `null` closes the menu.

Controlled open panel

[Complete TSX file](/examples/navigation-menu/controlled.tsx)

```tsx
"use client";

import { useRef, useState } from "react";
import { Button } from "@/siteplane/components/button";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuPopup,
  NavigationMenuTrigger,
} from "@/siteplane/components/navigation-menu";

export default function Example() {
  const anchor = useRef<HTMLUListElement>(null);
  const [value, setValue] = useState<string | null>(null);

  return (
    <div className="grid justify-items-start gap-4">
      <div className="flex flex-wrap items-center gap-2">
        <Button onClick={() => setValue("resources")} variant="outline">
          Open Resources
        </Button>
        <Button onClick={() => setValue(null)} variant="ghost">
          Close
        </Button>
        <p className="text-sm text-muted-foreground">
          Open item: {value ?? "none"}
        </p>
      </div>
      <NavigationMenu
        aria-label="Main navigation"
        onValueChange={setValue}
        value={value}
      >
        <NavigationMenuList ref={anchor}>
          <NavigationMenuItem value="product">
            <NavigationMenuTrigger>Product</NavigationMenuTrigger>
            <NavigationMenuContent>
              <div className="grid w-80 max-w-full gap-1 p-4">
                <NavigationMenuLink
                  description="See what is happening across your workspace."
                  href="#overview"
                >
                  Overview
                </NavigationMenuLink>
                <NavigationMenuLink
                  description="Track work from planning to launch."
                  href="#projects"
                >
                  Projects
                </NavigationMenuLink>
              </div>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem value="resources">
            <NavigationMenuTrigger>Resources</NavigationMenuTrigger>
            <NavigationMenuContent>
              <div className="grid w-80 max-w-full gap-1 p-4">
                <NavigationMenuLink
                  description="Guides and references for every feature."
                  href="#documentation"
                >
                  Documentation
                </NavigationMenuLink>
                <NavigationMenuLink
                  description="The latest improvements and releases."
                  href="#changelog"
                >
                  Changelog
                </NavigationMenuLink>
              </div>
            </NavigationMenuContent>
          </NavigationMenuItem>
        </NavigationMenuList>
        <NavigationMenuPopup anchor={anchor} />
      </NavigationMenu>
    </div>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| NavigationMenu | Base UI NavigationMenu.Root props | nav | Navigation landmark. Give it an aria-label; the other props follow Base UI. |
| NavigationMenu.value / defaultValue | string \| null | null | Controlled or initial open item, matched to NavigationMenuItem value. null means closed. |
| NavigationMenu.onValueChange | (value, eventDetails) => void | unset | Called when an item opens or closes. |
| NavigationMenu.delay / closeDelay | number | 50 / 50 | Milliseconds before hover opens or closes a panel. |
| NavigationMenuList | ul props | flex row, 4 px gap | Row of items. Pass a ref to anchor the popup to the whole row. |
| NavigationMenuItem.value | string | generated | Identifies the item. Set it when you control the menu. |
| NavigationMenuTrigger | Base UI Trigger props | ghost Button, navigation-link profile | Opens the item's panel on hover or press and adds a chevron that turns while it is open. |
| NavigationMenuLink.href | string | unset | Destination of the anchor. |
| NavigationMenuLink.active | boolean | false | Marks the current page with aria-current page and shows the persistent fill with the full foreground. |
| NavigationMenuLink.popup | boolean | true inside NavigationMenuContent, false elsewhere | Panel row style: full width and aligned to the start. Set false for a bar link inside a panel. |
| NavigationMenuLink.description | ReactNode | unset | Supporting text under the label. Beside a leading icon it starts under the label text. |
| NavigationMenuLink.closeOnClick | boolean | true | Closes the open panel when the link is pressed. |
| NavigationMenuContent.keepMounted | boolean | false | Keeps the panel in the DOM while closed, so it exists in server-rendered markup. |
| NavigationMenuPopup.anchor | Base UI Positioner anchor | trigger | Element the shared popup aligns to. Pass the list ref to align it with the row. |
Parts accept the props of their Base UI counterparts and string `className`.
Trigger and link render Buttons, so they share the Button's focus, hover and
pressed states. A `render` prop on either replaces the element inside that
Button, such as the link's anchor with your router's link, and the Button keeps
its styling; a render function receives the part's state. The popup renders in a
portal 8 px below the row, aligned to its start edge, with 16 px of collision
padding. Render exactly one `NavigationMenuPopup` inside `NavigationMenu`.

[Read the installed source](/source/navigation-menu).

## Usage notes

Use a Navigation Menu for site-level destinations with panels of links. Use a [Menu](/docs/components/menu/) for actions, and [Tabs](/docs/components/tabs/) to switch content within a page. Triggers only open panels; put every destination inside a panel or beside the triggers as a link.

The Left and Right arrow keys move between the triggers and links of the row. Enter or Space on a trigger opens its panel and moves focus to the first link, the Up and Down arrow keys move through the links, and Escape closes the panel and returns focus to the trigger. Set `active` on the link of the current page so it is announced with `aria-current="page"` and keeps its persistent fill.

Use real destination URLs in your application. These examples use fragment destinations to keep the preview local. Opening and switching panels fades and moves the content a few pixels; reduced motion removes the movement.
