Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

MenusReact

Navigation Menu

A collection of links and menus for website navigation.

Rendered by React 19.3.1

Loading React…
import { NavigationMenu } from '@hamur/react/navigation-menu'

const guides = [
  { href: '#quick-start', title: 'Quick start', text: 'Install and build your first part.' },
  { href: '#accessibility', title: 'Accessibility', text: 'How the parts stay accessible.' },
]
const handbook = [
  { href: '#styling', title: 'Styling', text: 'Plain CSS, Tailwind or CSS modules.' },
  { href: '#animation', title: 'Animation', text: 'Transitions on the data attributes.' },
  { href: '#composition', title: 'Composition', text: 'Swap in your own elements.' },
]

export default function NavigationMenuExample() {
  return (
    <NavigationMenu.Root className="nav">
      <NavigationMenu.List className="nav-list">
        {[
          { value: 'guides', label: 'Guides', links: guides },
          { value: 'handbook', label: 'Handbook', links: handbook },
        ].map((item) => (
          <NavigationMenu.Item key={item.value} value={item.value}>
            <NavigationMenu.Trigger className="nav-trigger">
              {item.label}
              <NavigationMenu.Icon className="nav-icon" />
            </NavigationMenu.Trigger>
            <NavigationMenu.Content className="nav-content">
              {item.links.map((link) => (
                <NavigationMenu.Link
                  key={link.href}
                  className="nav-link"
                  href={link.href}
                >
                  <span className="nav-link-title">{link.title}</span>
                  <span className="muted">{link.text}</span>
                </NavigationMenu.Link>
              ))}
            </NavigationMenu.Content>
          </NavigationMenu.Item>
        ))}
        <NavigationMenu.Item>
          <NavigationMenu.Link className="nav-trigger" href="#changelog">
            Changelog
          </NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
      <NavigationMenu.Portal>
        <NavigationMenu.Positioner
          className="nav-positioner"
          sideOffset={8}
          collisionPadding={16}
        >
          <NavigationMenu.Popup className="nav-popup">
            <NavigationMenu.Arrow className="arrow" />
            <NavigationMenu.Viewport className="nav-viewport" />
          </NavigationMenu.Popup>
        </NavigationMenu.Positioner>
      </NavigationMenu.Portal>
    </NavigationMenu.Root>
  )
}
599Tests passing in React · 0 failing
44.5 KiBSize
Not measured yetMemory
Not measured yetSpeed
productdevbook and 22 upstream authors10 commits · last

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>
        <NavigationMenu.Icon />
      </NavigationMenu.Trigger>
      <NavigationMenu.Content>
        <NavigationMenu.Link />
      </NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>
  <NavigationMenu.Portal>
    <NavigationMenu.Positioner>
      <NavigationMenu.Popup>
        <NavigationMenu.Arrow />
        <NavigationMenu.Viewport />
      </NavigationMenu.Popup>
    </NavigationMenu.Positioner>
  </NavigationMenu.Portal>
</NavigationMenu.Root>

// Not in this example: NavigationMenu.Backdrop

Examples

Nested

A Root inside a Content part makes a second level that opens to the side.

NavigationMenuNested.tsx
import { NavigationMenu } from '@hamur/react/navigation-menu'

const overviewLinks = [
  {
    href: '#quick-start',
    title: 'Quick Start',
    description: 'Install and assemble your first component.',
  },
  {
    href: '#accessibility',
    title: 'Accessibility',
    description: 'Learn how we build accessible components.',
  },
  {
    href: '#releases',
    title: 'Releases',
    description: 'See what’s new in the latest Base UI versions.',
  },
]

const handbookLinks = [
  {
    href: '#styling',
    title: 'Styling',
    description:
      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',
  },
  {
    href: '#animation',
    title: 'Animation',
    description:
      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',
  },
  {
    href: '#composition',
    title: 'Composition',
    description:
      'Base UI components can be replaced and composed with your own existing components.',
  },
]

export default function NavigationMenuNestedExample() {
  return (
    <NavigationMenu.Root className="nav">
      <NavigationMenu.List className="nav-list">
        <NavigationMenu.Item>
          <NavigationMenu.Trigger className="nav-trigger">
            Overview
            <NavigationMenu.Icon className="nav-icon" />
          </NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <ul className="nav-card-list wide">
              {overviewLinks.map((item) => (
                <li key={item.href}>
                  <NavigationMenu.Link className="nav-card" href={item.href}>
                    <h3 className="nav-card-title">{item.title}</h3>
                    <p className="nav-card-text">{item.description}</p>
                  </NavigationMenu.Link>
                </li>
              ))}
              <li>
                <NavigationMenu.Root orientation="vertical">
                  <NavigationMenu.List className="nav-inline-list">
                    <NavigationMenu.Item>
                      <NavigationMenu.Trigger className="nav-card">
                        <span className="nav-card-title">Handbook</span>
                        <span className="nav-card-text">How to use Base UI effectively.</span>
                        <NavigationMenu.Icon className="nav-icon" />
                      </NavigationMenu.Trigger>
                      <NavigationMenu.Content>
                        <ul className="nav-card-list narrow">
                          {handbookLinks.map((item) => (
                            <li key={item.href}>
                              <NavigationMenu.Link className="nav-card" href={item.href}>
                                <h3 className="nav-card-title">{item.title}</h3>
                                <p className="nav-card-text">{item.description}</p>
                              </NavigationMenu.Link>
                            </li>
                          ))}
                        </ul>
                      </NavigationMenu.Content>
                    </NavigationMenu.Item>
                  </NavigationMenu.List>
                  <NavigationMenu.Portal>
                    <NavigationMenu.Positioner
                      className="nav-positioner"
                      sideOffset={8}
                      alignOffset={-8}
                      align="end"
                      side="right"
                    >
                      <NavigationMenu.Popup className="nav-popup">
                        <NavigationMenu.Viewport className="nav-viewport" />
                      </NavigationMenu.Popup>
                    </NavigationMenu.Positioner>
                  </NavigationMenu.Portal>
                </NavigationMenu.Root>
              </li>
            </ul>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
      </NavigationMenu.List>
      <NavigationMenu.Portal>
        <NavigationMenu.Positioner
          className="nav-positioner"
          sideOffset={10}
          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
        >
          <NavigationMenu.Popup className="nav-popup">
            <NavigationMenu.Arrow className="arrow" />
            <NavigationMenu.Viewport className="nav-viewport" />
          </NavigationMenu.Popup>
        </NavigationMenu.Positioner>
      </NavigationMenu.Portal>
    </NavigationMenu.Root>
  )
}

Nested Inline

A second level that stays in the same panel: List and Viewport with a defaultValue, no Portal.

NavigationMenuNestedInline.tsx
import { NavigationMenu } from '@hamur/react/navigation-menu'

const audienceMenus = [
  {
    value: 'developers',
    label: 'Developers',
    hint: 'Go from idea to UI faster.',
    title: 'Build product UI without giving up control',
    description:
      'Start with accessible parts and shape them to your app instead of working around a preset design system.',
    links: [
      {
        href: '#quick-start',
        title: 'Quick start',
        description: 'Install Base UI and get your first interactive primitive on screen fast.',
      },
      {
        href: '#composition',
        title: 'Composition',
        description: 'Wrap and combine parts to match your product structure without hacks.',
      },
    ],
  },
  {
    value: 'systems',
    label: 'Design Systems',
    hint: 'Keep patterns aligned across teams.',
    title: 'Turn shared standards into working components',
    description:
      'Connect tokens, states, and accessibility rules once, then give every product team the same solid starting point.',
    links: [
      {
        href: '#styling',
        title: 'Styling',
        description: 'Map tokens and component states to your own CSS or utility setup.',
      },
      {
        href: '#accessibility',
        title: 'Accessibility',
        description: 'Review keyboard support and semantic defaults before anything ships.',
      },
      {
        href: '#tooltip',
        title: 'Tooltip',
        description: 'Set one clear pattern for lightweight help, hints, and field guidance.',
      },
      {
        href: '#popover',
        title: 'Popover',
        description: 'Handle richer anchored panels like menus, inspectors, and onboarding.',
      },
    ],
  },
  {
    value: 'managers',
    label: 'Engineering Leads',
    hint: 'Roll out shared UI without drag.',
    title: 'Give squads clear defaults and room to move',
    description:
      'Use the docs to align on quality bars, upgrades, and extension points while still leaving teams space to customize.',
    links: [
      {
        href: '#releases',
        title: 'Releases',
        description: 'Track version changes and migration notes before upgrades surprise teams.',
      },
      {
        href: '#typescript',
        title: 'TypeScript',
        description: 'See how the primitives type custom wrappers and shared abstractions.',
      },
      {
        href: '#forms',
        title: 'Forms',
        description: 'Standardize validation and field patterns teams reach for constantly.',
      },
    ],
  },
  {
    value: 'startups',
    label: 'Startups',
    hint: 'Ship polished basics while things change.',
    title: 'Get sturdy UI foundations in place early',
    description:
      'Cover the hard interaction details now so your team can spend more time on the product ideas that actually differentiate you.',
    links: [
      {
        href: '#quick-start',
        title: 'Quick start',
        description: 'Get the package installed and your first component working in minutes.',
      },
      {
        href: '#menu',
        title: 'Menu',
        description: 'Add action menus with keyboard support and focus handling already done.',
      },
      {
        href: '#dialog',
        title: 'Dialog',
        description: 'Launch settings or upgrade flows without rebuilding focus management.',
      },
    ],
  },
]

const guidesPanel = {
  title: 'Where teams usually start',
  description:
    'These are the docs people reach for first when they are turning a prototype into shared UI.',
}

const guideLinks = [
  {
    href: '#accessibility',
    title: 'Accessibility handbook',
    description: 'Take a practical pass over focus order, semantics, and keyboard support.',
  },
  {
    href: '#composition',
    title: 'Composition handbook',
    description: 'Learn when to wrap parts, share behavior, and expose flexible APIs.',
  },
  {
    href: '#styling',
    title: 'Styling handbook',
    description: 'Apply tokens and state styles without fighting the underlying markup.',
  },
]

export default function NavigationMenuNestedInlineExample() {
  return (
    <NavigationMenu.Root className="nav">
      <NavigationMenu.List className="nav-list">
        <NavigationMenu.Item>
          <NavigationMenu.Trigger className="nav-trigger">
            Product
            <NavigationMenu.Icon className="nav-icon" />
          </NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <NavigationMenu.Root orientation="vertical" defaultValue="developers">
              <div className="nav-inline">
                <NavigationMenu.List className="nav-inline-list">
                  {audienceMenus.map((menu) => (
                    <NavigationMenu.Item key={menu.value} value={menu.value}>
                      <NavigationMenu.Trigger className="nav-card">
                        <span className="nav-card-title">{menu.label}</span>
                        <span className="nav-card-text">{menu.hint}</span>
                      </NavigationMenu.Trigger>
                      <NavigationMenu.Content className="nav-inline-content">
                        <div>
                          <h4 className="nav-card-title">{menu.title}</h4>
                          <p className="nav-card-text">{menu.description}</p>
                        </div>
                        <ul className="nav-card-list">
                          {menu.links.map((link) => (
                            <li key={link.href}>
                              <NavigationMenu.Link className="nav-card" href={link.href}>
                                <h5 className="nav-card-title">{link.title}</h5>
                                <p className="nav-card-text">{link.description}</p>
                              </NavigationMenu.Link>
                            </li>
                          ))}
                        </ul>
                      </NavigationMenu.Content>
                    </NavigationMenu.Item>
                  ))}
                </NavigationMenu.List>
                <NavigationMenu.Viewport className="nav-inline-viewport" />
              </div>
            </NavigationMenu.Root>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Trigger className="nav-trigger">
            Learn
            <NavigationMenu.Icon className="nav-icon" />
          </NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <div className="nav-inline-content">
              <div>
                <h4 className="nav-card-title">{guidesPanel.title}</h4>
                <p className="nav-card-text">{guidesPanel.description}</p>
              </div>
              <ul className="nav-card-list">
                {guideLinks.map((link) => (
                  <li key={link.href}>
                    <NavigationMenu.Link className="nav-card" href={link.href}>
                      <h5 className="nav-card-title">{link.title}</h5>
                      <p className="nav-card-text">{link.description}</p>
                    </NavigationMenu.Link>
                  </li>
                ))}
              </ul>
            </div>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Link className="nav-trigger" href="#releases">
            Releases
          </NavigationMenu.Link>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Link className="nav-trigger" href="https://github.com/mui/base-ui">
            GitHub
          </NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
      <NavigationMenu.Portal>
        <NavigationMenu.Positioner
          className="nav-positioner"
          sideOffset={10}
          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
          collisionAvoidance={{ side: 'none' }}
        >
          <NavigationMenu.Popup className="nav-popup">
            <NavigationMenu.Arrow className="arrow" />
            <NavigationMenu.Viewport className="nav-viewport" />
          </NavigationMenu.Popup>
        </NavigationMenu.Positioner>
      </NavigationMenu.Portal>
    </NavigationMenu.Root>
  )
}

API reference

28 props across 13 parts, in React syntax. className, style, render are listed once, under Shared props.

ArrowNavigationMenu.Arrow

Displays an element pointing toward the navigation menu's current anchor. Renders a <div>.

Arrow data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-sideIndicates which side the popup is positioned relative to the trigger.
data-alignIndicates how the popup is aligned relative to specified side.
data-uncenteredPresent when the popup arrow is uncentered.

BackdropNavigationMenu.Backdrop

A backdrop for the navigation menu popup. Renders a <div>.

Backdrop data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-starting-stylePresent when the popup begins animating in.
data-ending-stylePresent when the popup is animating out.

ContentNavigationMenu.Content

A container for the content of the navigation menu item that is moved into the popup when the item is active. Renders a <div>.

Content props
PropTypeDefault
keepMountedWhether to keep the content mounted in the DOM while the popup is closed.booleanfalse
Content data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-starting-stylePresent when the content begins animating in.
data-ending-stylePresent when the content is animating out.
data-activation-directionWhich direction another trigger was activated from.

IconNavigationMenu.Icon

An icon that indicates that the trigger button opens a menu.

Icon data attributes
Data attributeDescription
data-popup-openPresent when the navigation menu is open and the item is active.

ItemNavigationMenu.Item

An individual navigation menu item. Renders a <li>.

Item props
PropTypeDefault
valueA unique value that identifies this navigation menu item.any—

A link in the navigation menu that can be used to navigate to a different page or section. Renders an <a>.

Link props
PropTypeDefault
closeOnClickWhether to close the navigation menu when the link is clicked.booleanfalse
activeWhether the link is the currently active page.booleanfalse
Link data attributes
Data attributeDescription
data-activePresent when the link is the currently active page.

ListNavigationMenu.List

Contains a list of navigation menu items. Renders an <ul>.

PopupNavigationMenu.Popup

A container for the navigation menu contents. Renders a <nav>.

Popup data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-starting-stylePresent when the popup begins animating in.
data-ending-stylePresent when the popup is animating out.
data-anchor-hiddenPresent when the anchor is hidden.
data-sideIndicates which side the popup is positioned relative to the trigger.
data-alignIndicates how the popup is aligned relative to the specified side.
Popup CSS variables
CSS variableDescription
--popup-widthThe fixed width of the popup element.
--popup-heightThe fixed height of the popup element.

PortalNavigationMenu.Portal

A portal element that moves the popup to a different part of the DOM. By default, the portal element is appended to <body>. Renders a <div>.

Portal props
PropTypeDefault
containerA parent element to render the portal element into.HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null—
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden.booleanfalse

PositionerNavigationMenu.Positioner

Positions the navigation menu against the currently active trigger. Renders a <div>.

Positioner props
PropTypeDefault
disableAnchorTrackingWhether to disable the popup from tracking any layout shift of its positioning anchor.booleanfalse
alignHow to align the popup relative to the specified side.Align'center'
alignOffsetAdditional offset along the alignment axis in pixels.number | OffsetFunction0
sideWhich side of the anchor element to align the popup against.Side'bottom'
sideOffsetDistance between the anchor and the popup in pixels.number | OffsetFunction0
arrowPaddingMinimum distance to maintain between the arrow and the edges of the popup.number5
anchorAn element to position the popup against.Element | VirtualElement | React.RefObject<Element | null> | (() => Element | VirtualElement | null) | null—
collisionAvoidanceDetermines how to handle collisions when positioning the popup.CollisionAvoidance—
collisionBoundaryAn element or a rectangle that delimits the area that the popup is confined to.Boundary'clipping-ancestors'
collisionPaddingAdditional space to maintain from the edge of the collision boundary.Padding5
stickyWhether to maintain the popup in the viewport afterbooleanfalse
positionMethodDetermines which CSS position property to use."absolute" | "fixed"'absolute'
Positioner data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-anchor-hiddenPresent when the anchor is hidden.
data-sideIndicates which side the popup is positioned relative to the trigger.
data-alignIndicates how the popup is aligned relative to the specified side.
data-instantPresent if animations should be instant.
Positioner CSS variables
CSS variableDescription
--available-widthThe available width between the trigger and the edge of the viewport.
--available-heightThe available height between the trigger and the edge of the viewport.
--anchor-widthThe anchor's width.
--anchor-heightThe anchor's height.
--transform-originThe coordinates that this element is anchored to. Used for animations and transitions.
--positioner-widthThe fixed width of the positioner element.
--positioner-heightThe fixed height of the positioner element.

RootNavigationMenu.Root

Groups all parts of the navigation menu. at the root, or <div> element when nested. Renders a <nav>.

Root props
PropTypeDefault
defaultValueThe uncontrolled value of the item that should be initially selected.TValue | nullnull
valueThe controlled value of the navigation menu item that should be currently open.TValue | nullnull
onValueChangeCallback fired when the value changes.((value: TValue | null, eventDetails: NavigationMenuRootChangeEventDetails) => void)—
actionsRefA ref to imperative actions.React.RefObject<NavigationMenuRootActions | null>—
onOpenChangeCompleteEvent handler called after any animations complete when the navigation menu is closed.((open: boolean) => void)—
delayHow long to wait before opening the navigation popup.number50
closeDelayHow long to wait before closing the navigation popup.number50
orientationThe orientation of the navigation menu."horizontal" | "vertical"'horizontal'

TriggerNavigationMenu.Trigger

Opens the navigation menu popup when hovered or clicked, revealing the associated content. Renders a <button>.

Trigger props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
disabledWhether the component should ignore user interaction.booleanfalse
Trigger data attributes
Data attributeDescription
data-popup-openPresent when the corresponding navigation menu is open.
data-pressedPresent when the trigger is pressed.
data-disabledPresent when the trigger is disabled.

ViewportNavigationMenu.Viewport

The clipping viewport of the navigation menu's current content. Renders a <div>.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
ArrowDownEnterSpaceOpens the item's content and keeps focus on the triggerTrigger
ArrowLeftArrowRightMoves between the triggers and links of the listList
ArrowDownArrowUpMoves between the triggers of a nested menuList
EscapeCloses the content and returns focus to its triggerTrigger

Accessibility

What the parts set
PartSetsSo that
Rootrenders a <nav>the menu is a navigation landmark
Listrenders a <ul>, each Item an <li>a screen reader counts the entries
Triggeraria-expanded, aria-controls to the popup while its content is opena screen reader says whether the panel is open and where it is
Linkaria-current="page" while activethe link to the current page is announced as such
Iconaria-hidden="true"the decorative chevron is not read
Arrowaria-hidden="true"the decorative arrow is not read
Backdroprole="presentation"the overlay is not read

Focus order

  1. Each Trigger and top-level Link is in the tab order; arrow keys also move between them.
  2. Opening with a key keeps focus on Trigger; Tab then moves into the open content.
  3. Escape closes the content and returns focus to its Trigger.