Navigation Menu

Pro

Menus 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#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add navigation-menu

Usage#

This example is part of Koala UI Pro

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.

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.

PropTypeDefaultDescription
viewportbooleantrueRenders 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.
delayDurationnumber100Milliseconds the pointer rests on a trigger before the first panel opens.
skipDelayDurationnumber300Window after a close in which the next trigger opens at once.
Data attributeDescription
data-viewporttrue or false, mirroring the viewport prop.
data-orientationhorizontal, the bar's direction.

NavigationMenuList

The row of entries, a <ul>. Also the rail inside a NavigationMenuSub. Forwards Radix NavigationMenu.List props.

Data attributeDescription
data-orientationhorizontal 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 attributeDescription
data-stateopen while its panel is open, otherwise closed.
data-disabledPresent 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 attributeDescription
data-stateopen or closed.
data-motionfrom-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).

PropTypeDefaultDescription
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.
activeboolean-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 attributeDescription
data-activePresent 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.

PropTypeDefaultDescription
orientation"vertical" | "horizontal""vertical"Which way the rail runs.
defaultValuestring-The pane showing the moment the panel opens.
valuestring-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 attributeDescription
data-statevisible 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 attributeDescription
data-stateopen or closed, for the zoom in and out.
data-instantOn the positioner while the first panel opens, so it lands in place; only a switch between triggers glides.
CSS variableDescription
--navigation-menu-xOn the positioner: the panel's offset from the bar's start edge.
--navigation-menu-origin-xOn the positioner: the open trigger's centre within the panel, the zoom's origin.
--navigation-menu-available-heightOn the positioner: the room under the bar; a taller panel scrolls inside itself.
--radix-navigation-menu-viewport-widthThe active content's measured width, set by Radix; the panel eases to it.
--radix-navigation-menu-viewport-heightThe active content's measured height, set by Radix.

FAQ#

DropdownMenu opens on click and every instance is its own popover: right for a single menu, like a Navbar's Collections link. NavigationMenu opens on hover and shares one panel across every trigger, so crossing from one to the next resizes that panel instead of swapping popovers.