Navigation Menu
ProMenus that open on hover and share one floating panel, so moving between triggers resizes and slides the same surface instead of opening a new popover.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuTrigger,
NavigationMenuContent,
NavigationMenuGroup,
NavigationMenuGroupLabel,
NavigationMenuLink,
NavigationMenuLinkIcon,
NavigationMenuLinkMedia,
NavigationMenuLinkText,
NavigationMenuLinkDescription,
NavigationMenuFooter,
NavigationMenuSub,
NavigationMenuSubViewport,
NavigationMenuIndicator,
} from "@/components/ui/navigation-menu"
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger />
<NavigationMenuContent>
<NavigationMenuGroup>
<NavigationMenuGroupLabel />
<NavigationMenuLink>
<NavigationMenuLinkIcon />
<NavigationMenuLinkText>
…
<NavigationMenuLinkDescription />
</NavigationMenuLinkText>
</NavigationMenuLink>
<NavigationMenuLink variant="card">
<NavigationMenuLinkMedia />
<NavigationMenuLinkText>…</NavigationMenuLinkText>
</NavigationMenuLink>
</NavigationMenuGroup>
<NavigationMenuFooter />
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger />
<NavigationMenuContent>
<NavigationMenuSub>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger />
<NavigationMenuContent>…</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
<NavigationMenuSubViewport />
</NavigationMenuSub>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink variant="bar" />
</NavigationMenuItem>
<NavigationMenuIndicator />
</NavigationMenuList>
</NavigationMenu>Columns#
For a wide menu, set NavigationMenuGroups side by side, each under a NavigationMenuGroupLabel, and close the panel with a NavigationMenuFooter for the link to everything else.
Featured card#
variant="card" turns a link into a featured block: a NavigationMenuLinkMedia picture over its title and description, beside the rows. The whole card is the link.
Category rail#
A NavigationMenuSub nests a menu inside the panel: a rail of categories down the side and a pane that switches the moment the pointer reaches the next one, with no delay. Its defaultValue picks the pane that shows first.
Icon tiles#
NavigationMenuLinkIcon sets a row's glyph in a 32px tile. A brand logo keeps its own colours there, where a bare icon in a row is muted, so it's the pick for integrations and apps.
Timing#
A trigger opens after delayDuration (100ms), so a cursor passing over the bar doesn't flash every menu. Reach a sibling trigger within skipDelayDuration (300ms) and it opens at once.
Without the shared panel#
viewport={false} gives each trigger its own panel, opened under it. Switching closes one and opens the next, so reach for it only when the menus are far apart.
Density#
density on the root sets the row height of every link in the menu. The panel padding stays put, so the corners stay concentric in both.
API reference#
NavigationMenu
The root, over Radix NavigationMenu.Root (forwards value, defaultValue, onValueChange, dir). Every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| viewport | boolean | true | Renders the shared panel every content mounts into. false opens each content as its own panel under its trigger. |
| density | "comfortable" | "compact" | - | Row height for every link in the menu. Inherits the nearest DensityProvider (compact by default) when unset. |
| delayDuration | number | 100 | Milliseconds the pointer rests on a trigger before the first panel opens. |
| skipDelayDuration | number | 300 | Window after a close in which the next trigger opens at once. |
| Data attribute | Description |
|---|---|
| data-viewport | true or false, mirroring the viewport prop. |
| data-orientation | horizontal, the bar's direction. |
NavigationMenuList
The row of entries, a <ul>. Also the rail inside a NavigationMenuSub. Forwards Radix NavigationMenu.List props.
| Data attribute | Description |
|---|---|
| data-orientation | horizontal in the bar, vertical in a Sub rail. |
NavigationMenuItem
One entry: a trigger with its content, or a single NavigationMenuLink variant="bar". Forwards Radix NavigationMenu.Item props, including value.
NavigationMenuTrigger
The chip in the bar, with a caret that turns while its panel is open. Forwards Radix NavigationMenu.Trigger props.
| Data attribute | Description |
|---|---|
| data-state | open while its panel is open, otherwise closed. |
| data-disabled | Present when disabled. |
NavigationMenuContent
One panel's contents; give its wrapper a width (w-56, w-72) to size the panel. Forwards Radix NavigationMenu.Content props.
| Data attribute | Description |
|---|---|
| data-state | open or closed. |
| data-motion | from-start, from-end, to-start or to-end as the panel switches between triggers, driving the sideways slide. |
NavigationMenuLink
A link that closes the panel on select and joins the arrow-key focus. Forwards Radix NavigationMenu.Link props (href, asChild).
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "row" | "bar" | "card" | "row" | row is a row in a panel; bar a plain entry beside the triggers; card a featured block over a NavigationMenuLinkMedia. |
| active | boolean | - | Marks the current page: aria-current and the accent fill. |
| density | "comfortable" | "compact" | - | Row height for this link. Inherits the menu's density when unset. |
| Data attribute | Description |
|---|---|
| data-active | Present when active. |
NavigationMenuLinkText
The title and description column of a described row, after the leading icon. Renders a <span>.
NavigationMenuLinkDescription
The muted sentence under a row's title; place it inside NavigationMenuLinkText. Renders a <span>.
NavigationMenuLinkIcon
A ringed 32px tile for a row's glyph, where a brand logo keeps its colours. aria-hidden. Renders a <span>.
NavigationMenuLinkMedia
The inset picture frame of a card link. Put an <img> inside; it owns an aspect-video ratio you can override.
NavigationMenuGroup
A column of links, role="group". Forwards native <div> props.
NavigationMenuGroupLabel
A group's small muted heading. Optional. Forwards native <div> props.
NavigationMenuFooter
The panel's foot under a hairline; the rule drops when nothing sits above it. Forwards native <div> props.
NavigationMenuSub
A menu inside a panel: a NavigationMenuList rail, then a NavigationMenuSubViewport. The pane switches as the pointer moves down the rail. Forwards Radix NavigationMenu.Sub props.
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "vertical" | "horizontal" | "vertical" | Which way the rail runs. |
| defaultValue | string | - | The pane showing the moment the panel opens. |
| value | string | - | The active pane, controlled. |
| onValueChange | (value: string) => void | - | Called when another pane is shown. |
NavigationMenuSubViewport
Where a Sub's active pane shows, sized to it. Place it after the rail. Forwards Radix NavigationMenu.Viewport props.
NavigationMenuIndicator
The tip that tracks the active trigger. Optional: drop it as the list's last child.
| Data attribute | Description |
|---|---|
| data-state | visible while a panel is open, otherwise hidden. |
NavigationMenuViewport
The shared panel, which the root already renders. It sits in a positioner that centres it under the open trigger, shifts it back inside whatever clips the menu, and caps it to the room below the bar.
| Data attribute | Description |
|---|---|
| data-state | open or closed, for the zoom in and out. |
| data-instant | On the positioner while the first panel opens, so it lands in place; only a switch between triggers glides. |
| CSS variable | Description |
|---|---|
| --navigation-menu-x | On the positioner: the panel's offset from the bar's start edge. |
| --navigation-menu-origin-x | On the positioner: the open trigger's centre within the panel, the zoom's origin. |
| --navigation-menu-available-height | On the positioner: the room under the bar; a taller panel scrolls inside itself. |
| --radix-navigation-menu-viewport-width | The active content's measured width, set by Radix; the panel eases to it. |
| --radix-navigation-menu-viewport-height | The active content's measured height, set by Radix. |