Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Navigation Menu for React

Site links in a horizontal bar, some opening a panel of related pages. For a docs or marketing header in React with Base UI, when the item is a destination.

Framework
UI library

Links for the site sit in one row. Some open a panel of related pages, and some are just a link.

Use it for destinations in a header. Commands such as Export or Delete belong in a Dropdown Menu. In React, Base UI renders a navigation landmark, and router links go in the link part so the focus behavior stays.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Navigation Menu anatomy: list, trigger, indicator, content, link
NavigationMenu
├── NavigationMenuList
│   ├── NavigationMenuItem
│   │   ├── NavigationMenuTrigger
│   │   └── NavigationMenuContent
│   │       └── NavigationMenuLink
│   └── NavigationMenuItem
│       └── NavigationMenuLink
└── NavigationMenuIndicator

Usage

import {
  NavigationMenu, NavigationMenuContent, NavigationMenuItem,
  NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
<NavigationMenu>
  <NavigationMenuList>
    <NavigationMenuItem>
      <NavigationMenuTrigger>Item One</NavigationMenuTrigger>
      <NavigationMenuContent>
        <NavigationMenuLink href="#">Link</NavigationMenuLink>
      </NavigationMenuContent>
    </NavigationMenuItem>
  </NavigationMenuList>
</NavigationMenu>

Accessibility

Enter
Activate the focused control.
Space
Activate the focused control.
Arrow Left
Move left.
Arrow Right
Move right.
Escape
Close the open panel.

Demos

Rtl

The header in Arabic: a getting-started list, a component grid that wraps into columns, and a docs link with no panel. dir is rtl on the wrapper, so the triggers run from the right and panels follow the reading order. Arrow keys follow that order too. Set direction on the container you ship, and check that a wide panel does not spill off the far edge.

Preview width

FAQ

How do I highlight the current page in the Navigation Menu?

Set aria-current="page" on the link that matches the current route. Screen readers announce it, and you can style it with Tailwind's aria-[current=page]: variant. active on NavigationMenuLink does both for you.

How do I change how fast the Base UI Navigation Menu opens on hover?

Set delay and closeDelay on NavigationMenu, in milliseconds (50 each by default). Raise them if panels open while people only pass over a trigger.

How do I control which Navigation Menu panel is open in React?

Pass value and onValueChange on NavigationMenu: value is the open item's value, and null closes every panel. closeOnClick on a NavigationMenuLink closes it when that link is chosen.

How do I make the Navigation Menu work on mobile?

Hide the bar below md and put the same links in a Sheet behind a menu button. Wide panels under a header bar don't suit small touch screens, and a sheet gives every link a full-width tap target.

When should I use a Navigation Menu instead of a Dropdown Menu or a Menubar?

Use the Navigation Menu for site navigation: links in a header, grouped under triggers. A Dropdown Menu holds actions like Export or Delete, and a Menubar is an app's File, Edit, View bar. Each gives assistive tech the roles that match what it does.

Is the Base UI Navigation Menu accessible?

Yes. It renders a nav landmark, Tab moves between triggers and links, Enter or Space opens a trigger's panel, and Escape closes it and returns focus to the trigger. Give each link text that makes sense on its own, since screen readers can list them out of context.

How do I install the Navigation Menu in React?

Run the command from the Installation section above: it adds the Navigation Menu and the parts it uses to your project's ui folder. The shadcn CLI writes components/ui/navigation-menu.tsx, a file you own and can edit.

API Reference

<nav> data-slot="navigation-menu"

PropType
value?Value | null

The controlled value of the navigation menu item that should be currently open. When non-nullish, the menu will be open. When nullish, the menu will be closed. To render an uncontrolled navigation menu, use the defaultValue prop instead.

Type
Value | null
Default
null
defaultValue?Value | null

The uncontrolled value of the item that should be initially selected. To render a controlled navigation menu, use the value prop instead.

Type
Value | null
Default
null
onValueChange?(value: Value | null, eventDetails: NavigationMenuRoot.ChangeEventDetails) → void

Callback fired when the value changes.

Type
(value: Value | null, eventDetails: NavigationMenuRoot.ChangeEventDetails) → void
Parameters

value – Value | null

eventDetails – NavigationMenuRoot.ChangeEventDetails

Returns
void
actionsRef?React.RefObject<NavigationMenuRoot.Actions | null>

A ref to imperative actions. - unmount: Ends the closing phase of the navigation menu popup after an externally controlled closing animation finishes. Call preventUnmountOnClose() in onValueChange first, otherwise the navigation menu popup completes closing on its own. Whether it leaves the DOM is decided by keepMounted on the portal. - close: Closes the navigation menu imperatively when called.

Type
React.RefObject<NavigationMenuRoot.Actions | null>
closeDelay?number

How long to wait before closing the navigation popup. Specified in milliseconds.

Default
50
delay?number

How long to wait before opening the navigation popup. Specified in milliseconds.

Default
50
onOpenChangeComplete?(open: boolean) → void

Event handler called after any animations complete when the navigation menu is closed.

Parameters

open – boolean

Returns
void
orientation?"horizontal" | "vertical"

The orientation of the navigation menu.

Type
"horizontal" | "vertical"
Default
"horizontal"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-orientationstring

Mirrors the orientation prop.

CSS variableType
--trigger-heightstring
--trigger-widthstring
--trigger-xstring
--trigger-ystring
--viewport-heightstring
--viewport-widthstring
--viewport-xstring
--viewport-ystring

<div>

CSS variableType
--available-widthstring

The width left between the anchor and the viewport's edge.

--popup-heightstring
--popup-widthstring
--positioner-heightstring
--positioner-widthstring
--transform-originstring

Where the content scales from, on its trigger's side.

<button> data-slot="navigation-menu-trigger"

PropType
disabled?boolean

Whether the component should ignore user interaction.

Default
false
nativeButton?boolean

Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).

Default
true
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
data-trigger-proxy-idstring

Id of the triggerProxy part.

data-uidstring

Mirrors the id prop.

data-valuestring

The item's value.

<div> data-slot="navigation-menu-content"

PropType
keepMounted?boolean

Whether to keep the content mounted in the DOM while the popup is closed. Ensures the content is present during server-side rendering for web crawlers.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-orientationstring

Mirrors the orientation prop.

data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
data-uidstring

Mirrors the id prop.

data-valuestring

The item's value.

<a> data-slot="navigation-menu-link"

PropType
active?boolean

Whether the link is the currently active page.

Default
false
closeOnClick?boolean

Whether to close the navigation menu when the link is clicked.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-current?string

Present when current is true.

data-ownedbystring

Id of the content part.

data-valuestring

The item's value.

data-slot="navigation-menu-list"

PropType
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-orientationstring

Mirrors the orientation prop.

data-slot="navigation-menu-item"

PropType
value?any

A unique value that identifies this navigation menu item. If no value is provided, a unique ID will be generated automatically. Use when controlling the navigation menu programmatically.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-orientationstring

Mirrors the orientation prop.

<div>

PropType
align?"start" | "center" | "end"

How to align the popup relative to the specified side. Ark default: center.

Type
"start" | "center" | "end"
Default
"start"
alignOffset?number | OffsetFunction

Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a data object parameter with the following properties: - data.anchor: the dimensions of the anchor element with properties width and height. - data.positioner: the dimensions of the positioner element with properties width and height. - data.side: which side of the anchor element the positioner is aligned against. - data.align: how the positioner is aligned relative to the specified side. ``jsx <Positioner alignOffset={({ side, align, anchor, positioner }) => { return side === top || side === bottom ? anchor.width : anchor.height; }} /> ``

Type
number | OffsetFunction
Default
0
anchor?Element | null | VirtualElement | React.RefObject<Element | null> | (() → Element | VirtualElement | null)

An element to position the popup against. By default, the popup will be positioned against the trigger.

Type
Element | null | VirtualElement | React.RefObject<Element | null> | (() → Element | VirtualElement | null)
arrowPadding?number

Minimum distance to maintain between the arrow and the edges of the popup. Use it to prevent the arrow element from hanging out of the rounded corners of a popup.

Default
5
collisionAvoidance?SideFlipMode | SideShiftMode

Determines how to handle collisions when positioning the popup. side controls overflow on the preferred placement axis (top/bottom or left/right): - flip: keep the requested side when it fits; otherwise try the opposite side (top and bottom, or left and right). - shift: never change side; keep the requested side and move the popup within the clipping boundary so it stays visible. - none: do not correct side-axis overflow. align controls overflow on the alignment axis (start/center/end): - flip: keep side, but swap start and end when the requested alignment overflows. - shift: keep side and requested alignment, then nudge the popup along the alignment axis to fit. - none: do not correct alignment-axis overflow. fallbackAxisSide controls fallback behavior on the perpendicular axis when the preferred axis cannot fit: - start: allow perpendicular fallback and try the logical start side first (top before bottom, or left before right in LTR). - end: allow perpendicular fallback and try the logical end side first (bottom before top, or right before left in LTR). - none: do not fallback to the perpendicular axis. When side is shift, explicitly setting align only supports shift or none. If align is omitted, it defaults to flip. ``jsx <Positioner collisionAvoidance={{ side: shift, align: shift, fallbackAxisSide: none, }} /> ``

Type
SideFlipMode | SideShiftMode
collisionBoundary?"clipping-ancestors" | Element | Element[] | Rect

An element or a rectangle that delimits the area that the popup is confined to.

Type
"clipping-ancestors" | Element | Element[] | Rect
Default
"clipping-ancestors"
collisionPadding?Padding

Additional space to maintain from the edge of the collision boundary.

Default
5
disableAnchorTracking?boolean

Whether to disable the popup from tracking any layout shift of its positioning anchor.

Default
false
positionMethod?"absolute" | "fixed"

Determines which CSS position property to use.

Type
"absolute" | "fixed"
Default
"absolute"
side?"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"

Which side of the anchor element to align the popup against. May automatically change to avoid collisions.

Type
"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"
Default
"bottom"
sideOffset?number | OffsetFunction

Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a data object parameter with the following properties: - data.anchor: the dimensions of the anchor element with properties width and height. - data.positioner: the dimensions of the positioner element with properties width and height. - data.side: which side of the anchor element the positioner is aligned against. - data.align: how the positioner is aligned relative to the specified side. ``jsx <Positioner sideOffset={({ side, align, anchor, positioner }) => { return side === top || side === bottom ? anchor.height : anchor.width; }} /> ` Ark default: 0`.

Type
number | OffsetFunction
Default
8
sticky?boolean

Whether to maintain the popup in the viewport after the anchor element was scrolled out of view.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

CSS variableType
--available-widthstring

The width left between the anchor and the viewport's edge.

--popup-heightstring
--popup-widthstring
--positioner-heightstring
--positioner-widthstring
--transform-originstring

Where the content scales from, on its trigger's side.

data-slot="navigation-menu-indicator"

PropType
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-orientationstring

Mirrors the orientation prop.

With other components

  • Free

    Button

    Buttons

    Trigger looks like a button

  • Free

    Sheet

    Overlays

    Same links on phones

  • Free

    Badge

    Feedback

    New next to a link

  • Free

    Card

    Layout

    Featured cell in mega menu

  • Free

    Separator

    Layout

    Split groups in a panel

  • Free

    Breadcrumb

    Navigation

    Trail under the header