Morpha UI

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-menu

Composition

<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.

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-menu

API Reference

Prop

Type

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 with aria-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 active so it is announced (aria-current) as well as highlighted.

On this page