Site navigation with rich dropdowns, wrapping the Base UI Navigation Menu. One shared popup morphs between the open item's content instead of popping a new one per item, and it's built for links: app actions belong in the Menu.

Playground

Every control maps to a prop. Hover between the items and watch the popup resize; the Code tab always shows the markup for exactly what you've configured.

Nested submenus

Open Product, then hover Integrations: a row with a chevron opens a second panel beside the first. That's a NavigationMenuRoot nested inside a NavigationMenuContent; its trigger reads as a row, and its content opens in a flyout with side="inline-end".

Nested inline submenus

Open Solutions: a list of audiences sits on the left and the chosen one's details on the right, swapping in place as you move down the list, with no second popup. That's a nested root with inline plus a NavigationMenuViewport beside its list and a defaultValue, so the selected item's content renders there instead of a flyout.

States

Closed and open are live, so hover or click a trigger. While open, the trigger carries data-popup-open (the chevron flips on it) and the incoming content carries data-activation-direction, left or right depending on which neighbor was open before; an item that is only a link has no open state at all.

Styling states

Triggers carry data-popup-open (the built-in chevron flips on it); the popup's size animates to the measured --popup-width/--popup-height, and content slides by data-activation-direction:

.ub-navigation-menu-popup {
  transition-duration: var(--ub-duration-slow);
}

Props

The root bundles the popup machinery, so the parts you write are the list and its items. The essentials:

PropTypeDefaultDescription
NavigationMenuRoot.delaynumber50Milliseconds before opening on hover (closeDelay mirrors it).
NavigationMenuRoot.orientation"horizontal" | "vertical""horizontal"Vertical stacks the list and swaps the arrow keys.
NavigationMenuRoot.sidePositionerSideโ€“Which side of the trigger the popup opens on; nested menus usually want inline-end. Also align.
NavigationMenuRoot.sideOffsetnumber8Gap between the trigger and the popup, in pixels.
NavigationMenuRoot.inlinebooleanfalseRenders children without the bundled popup; pair with a NavigationMenuViewport for same-panel submenus.
NavigationMenuTrigger.childrenReactNodeโ€“The item's label; the chevron renders automatically.
NavigationMenuLink.hrefstringโ€“A real anchor; render={<Link />} plugs in a router.

Agent instructions

Working with a coding agent? Paste this into its context, or point it at llms.txt for the whole library.

NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuViewport, from @usebones/react.
- Structure: NavigationMenuRoot wraps NavigationMenuList of NavigationMenuItems. An item holds a NavigationMenuTrigger (label as children; chevron automatic) + NavigationMenuContent, or just a NavigationMenuLink for a plain link.
- The popup machinery is bundled in the root; one shared popup morphs between the open item's content.
- Nested submenus: put another NavigationMenuRoot inside a NavigationMenuContent (side="inline-end" opens the flyout beside the panel). Same-panel submenus: nested root with inline plus a NavigationMenuViewport next to its list and a defaultValue.
- Links are real anchors: pass href, or render={<Link to="..." />} for a router. Fill content with NavigationMenuLinks in your own layout.
- For links only; app command surfaces use Menu.
- Restyle in CSS via .ub-navigation-menu-trigger, .ub-navigation-menu-link, .ub-navigation-menu-popup, [data-popup-open], [data-activation-direction]. Size morph runs on --popup-width/--popup-height; keep those transitions. Tokens only.