# Nav Menu

> A React navigation menu for websites: links and dropdown panels, lists or mega menus with a featured card, morphing through one panel.

- Type: React component (Navigation)
- Access: Free (the source is open to everyone)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add nav-menu`
- Docs: https://wingo-ui.com/components/nav-menu

## Interaction

Point at a menu and after a short delay its panel drops in; slide to the next one and the same panel glides under it, resizes to the new content and swaps it with a short slide in the direction you moved. A soft pill (or an underline) follows the pointer between triggers and rests on the section you are in. Click a link and the panel closes while the active item updates. With the keyboard, Tab reaches every trigger and link, ArrowLeft / ArrowRight move between triggers, Enter or Space opens a panel and Tab moves into it, ArrowDown opens it and lands on its first link, arrows move between its links and Escape closes it and returns focus. When the bar is narrower than the breakpoint (a phone) a hamburger takes the list's place: tap it and a full-screen menu slides in from the right with one accordion section per menu, 48px rows, the actions stacked full width at the bottom, and the section you are in already open. Choosing a link, tapping the close button, swiping the menu right or Escape closes it. Under reduced motion panels fade without sliding and resize at once.

## Installation

```bash
npx wingo-ui@latest add nav-menu
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/nav-menu.json`

Files:

- `components/ui/nav-menu.tsx`

npm dependencies: `npm i @radix-ui/react-navigation-menu motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Accordion](https://wingo-ui.com/components/accordion) ([Markdown](https://wingo-ui.com/components/accordion.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Sheet](https://wingo-ui.com/components/sheet) ([Markdown](https://wingo-ui.com/components/sheet.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { Calculator, FileText, Receipt } from "lucide-react"
import { Button } from "@/components/ui/button"
import { NavMenu, type NavMenuItem } from "@/components/ui/nav-menu"
import { NavLink } from "@/lib/navigation"

const items: NavMenuItem[] = [
  {
    label: "Product",
    items: [
      { label: "Invoicing", href: "/billing", icon: <FileText />, description: "Invoices and estimates in 30 seconds" },
      { label: "E-invoicing", href: "/e-invoicing", icon: <Receipt />, description: "Sent and validated automatically" },
      { label: "Reports", href: "/reports", icon: <Calculator />, description: "Payments, sales tax and overdue" },
    ],
    featured: {
      title: "New: e-invoices sent automatically",
      description: "Every invoice reaches your client with zero extra clicks.",
      image: "/demo/laptop.jpg",
      href: "/news/e-invoicing",
    },
  },
  { label: "Pricing", href: "/pricing" },
  { label: "Contact", href: "/contact" },
]

export function SiteHeader() {
  return (
    <header className="sticky top-0 z-sticky border-b border-foreground/10 bg-background px-4 md:px-6">
      <NavMenu
        items={items}
        logo={<NavLink href="/" className="text-[15px] font-semibold">SimpleInvoice</NavLink>}
        actions={
          <>
            <Button variant="ghost" size="sm">Log in</Button>
            <Button size="sm">Try it free</Button>
          </>
        }
      />
    </header>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `NavMenuItem[]` |  | Top-level entries, the shared NavItem shape (id, label, href, icon, description, badge, disabled, external, match, onSelect, items) plus content (replaces the generated panel), featured ({ title, description?, image?, href, badge? }: a 16:10 card next to the links, a compact row in the phone menu) and columns (1 \| 2 \| 3; default 2 above 4 links, else 1). An item with items (or content) becomes a dropdown whose panel lists them; with an href as well, the panel starts with a "View all" row to it. Otherwise it is a link. |
| `value?` | `string` |  | The active item's key (navItemKey: id, else href, else the label), at any depth. Controls the active state. |
| `defaultValue?` | `string` | `""` | The active item's key (navItemKey: id, else href, else the label) while uncontrolled and no current path is known; clicks then move it. Empty: the first enabled item. Ignored under a router: the playground drops its pretend router's path while this is set. Try /pricing or Solutions. |
| `onValueChange?` | `(key: string, item: NavItem) => void` |  | Fires on every link or row activation, in every mode (controlled, router, uncontrolled). |
| `currentPath?` | `string` | `""` | The current pathname. Empty: the LinkProvider's. With a path the router decides what is active (the longest matching href) and the top-level item above it gets the active look. Try /pricing or /product/reports. |
| `logo?` | `ReactNode` |  | The start slot, usually a link home. Repeated in the phone menu's header. |
| `actions?` | `ReactNode` |  | The end slot ("Log in", "Try it free"). In the phone menu the same buttons stack full width at 48px, the last one on top. |
| `mobileActions?` | `ReactNode` |  | Replaces actions in the phone menu's footer. |
| `viewport?` | `boolean` | `true` | One shared panel that follows the open trigger and morphs between menus (the Stripe header). Off: each panel opens as its own surface under its trigger. |
| `indicator?` | `"pill" \| "underline" \| "none"` | `"pill"` | The highlight that slides between hovered and open triggers on a smooth spring and rests on the active section: a soft pill, a 2px underline, or nothing (color only). |
| `size?` | `"sm" \| "md"` | `"md"` | Triggers 32 / 36px and the bar 56 / 64px. Triggers grow to 44px on touch screens. |
| `align?` | `"start" \| "center" \| "end"` | `"center"` | Where the list sits between logo and actions. center keeps it in the middle of the bar. |
| `mobileBreakpoint?` | `"md" \| "lg"` | `"md"` | Below this width of the bar (768 / 1024px) the hamburger and the full-screen menu replace the list and actions, with CSS container queries, so the server HTML is already right. Use lg when the items do not fit a tablet. |
| `delayDuration?` | `number` | `150` | Milliseconds of hover before a panel opens, so a pointer passing over the bar does not flash menus. |
| `skipDelayDuration?` | `number` | `300` | Milliseconds after leaving a trigger during which the next one opens at once, without the delay. |
| `defaultMobileOpen?` | `boolean` | `false` | Starts with the phone menu open (uncontrolled). |
| `mobileOpen?` | `boolean` |  | The phone menu's open state, controlled. Pair with onMobileOpenChange. |
| `onMobileOpenChange?` | `(open: boolean) => void` |  | Fires when the phone menu opens or closes (hamburger, close button, a chosen link, swipe, Escape). |
| `modal?` | `boolean` | `true` | The phone menu traps focus, locks the page scroll and dims the page. Turn it off for inline previews of the open menu. |
| `container?` | `HTMLElement \| null` |  | Where the phone menu portals to, e.g. a phone frame. Default: document.body. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | Renders every link through this component (a router Link) instead of the LinkProvider's. |
| `labels?` | `{ navigation?: string; openMenu?: string; closeMenu?: string; menu?: string; viewAll?: string }` |  | Every string: the nav's aria-label ("Main"), the hamburger ("Open menu" / "Close menu"), the phone menu's hidden title ("Menu") and the first row of a panel whose item has its own page ("View all"). |
| `classNames?` | `Partial<Record<"logo" \| "list" \| "item" \| "trigger" \| "link" \| "chevron" \| "indicator" \| "viewport" \| "content" \| "panelLink" \| "featured" \| "actions" \| "mobileTrigger" \| "sheet" \| "sheetLink" \| "sheetActions", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the bar (the <nav>), merged last. |

## Parts

### NavMenuItem (type)

One top-level entry: a NavItem (label, href, icon, description, badge, and items for the panel's links) plus content to replace the generated panel, featured for a highlighted card and columns (1, 2 or 3).

### NavMenuFeatured (type)

The card beside a panel's links: title, description, image (16:10), href and badge.

## Examples

- **Panels under their triggers**: viewport={false}: every menu opens its own panel under its trigger instead of one shared, morphing panel.
- **Underline indicator**: indicator="underline" with the list at the start; the line follows the pointer and rests on Pricing.
- **Phone menu**: Below the breakpoint the hamburger opens a full-screen menu: accordion sections, 48px rows, actions at the thumb.
- **App navigation**: A compact two-level nav for an app (size="sm"); the router path /invoices marks Sales and its row active.
- **Over a dark hero photo**: A transparent header on a photo: a dark scope turns the triggers white and the panel dark.

## See also

- [Marketing Header](https://wingo-ui.com/components/marketing-header) ([Markdown](https://wingo-ui.com/components/marketing-header.md))
- [Topbar](https://wingo-ui.com/components/topbar) ([Markdown](https://wingo-ui.com/components/topbar.md))
- [Sheet](https://wingo-ui.com/components/sheet) ([Markdown](https://wingo-ui.com/components/sheet.md))
- [Accordion](https://wingo-ui.com/components/accordion) ([Markdown](https://wingo-ui.com/components/accordion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Gooey Nav](https://wingo-ui.com/components/gooey-nav) ([Markdown](https://wingo-ui.com/components/gooey-nav.md))

---

Canonical: https://wingo-ui.com/components/nav-menu
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
