Navigation Menu
A horizontal site-navigation bar with rich dropdown panels.
Overview
Site navigation with hover/focus-opened panels that morph between sizes inside a
single shared popup. Triggers are type-label state-layer pills that flip to
secondary-container while open, with a chevron that rotates on the kit's 200ms
ease-out; the dropped panel is the kit's surface-container surface with 16px
corners, anchored under the active trigger. Panel rows are compact
type-body-sm links — a NavigationMenuLinkTitle over a
NavigationMenuLinkDescription — and the row for the current page takes the
high-emphasis secondary-container treatment via active.
$ npx morpha-ui add navigation-menuComposition
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[320px] gap-0.5">
<li>
<NavigationMenuLink href="/docs/button">
<NavigationMenuLinkTitle>Button</NavigationMenuLinkTitle>
<NavigationMenuLinkDescription>
Six MD3 styles with state layers.
</NavigationMenuLinkDescription>
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>NavigationMenu renders the portal, the positioner, the popup and the viewport
for you — every panel drops into that one shared surface, which tweens between
panel sizes. NavigationMenuPositioner is exported for the rare case you need
to place the popup yourself.
Examples
List
A single-column panel of title + description rows. NavigationMenuLink stacks
its children by default, so a row is just the two typographic parts.
Simple
Rows are plain links when they carry no description — the same state-layer treatment, no title/description pair.
With icon
Pass className="flex-row items-center gap-2" to flip a row from stacked to
compact. Icons are sized to 16px and tinted on-surface-variant unless you set
a size or color yourself.
Link
NavigationMenuLink renders an <a>. To route through your framework's link,
hand it over with render (Base UI's composition prop — there is no asChild):
the kit's styling and the menu's keyboard wiring come along. A top-level link
with no panel adopts the pill look through navigationMenuTriggerStyle.
Active
Mark the current page with active: the row — or the top-level pill — takes
secondary-container, and the description follows it onto the tonal background.
Alignment
align places the popup against the active trigger (start, center, end).
The panel still flips and shifts to stay in the viewport.
Styling
State lives on data attributes: data-popup-open on the trigger (the pill's
active treatment and the chevron rotation), data-active on the current page's
link, and data-activation-direction on the content — that last one is what
pushes the outgoing panel left or right as the popup morphs toward the newly
hovered trigger.
Coming from shadcn? There is no viewport={false} mode: Base UI always
anchors the panel to the active trigger, which is what the detached viewport
was approximating. The NavigationMenuIndicator is kept as a shim — Base UI
has no part that slides an arrow along the list.
Installation
Morpha UI components are distributed via the Morpha CLI: npx morpha-ui add navigation-menu.
npx morpha-ui add navigation-menuAPI Reference
NavigationMenuTrigger, NavigationMenuContent and NavigationMenuLink
forward their Base UI props (NavigationMenuLink adds active);
NavigationMenuLinkTitle and NavigationMenuLinkDescription forward div and
p props.
Accessibility
- Renders a
<nav>landmark with list semantics; panels are wired witharia-expanded/aria-controls(Base UI). - Keyboard: Tab/Arrow keys traverse triggers and links; Esc closes the open panel.
- Mark the current page's link with
activeso it is announced (aria-current) as well as highlighted.