Navigation Menu

A collection of links and panels for navigating a website, with keyboard support and an animated, resizing viewport.

Default

Each NavigationMenu.Item holds either a NavigationMenu.Trigger with its NavigationMenu.Content panel, or a single NavigationMenu.Link. Panels open on hover or click and share one viewport that resizes between them.

tsx

import { NavigationMenu } from "@hummingbirdui/react";

const components = ["Accordion", "Alert", "Button", "Card", "Dialog", "Tabs"];

export default function NavigationMenuDefault() {
  return (
    <NavigationMenu>
      <NavigationMenu.List>
        <NavigationMenu.Item>
          <NavigationMenu.Trigger>Getting started</NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <ul className="w-56">
              <li>
                <NavigationMenu.Link href="#">Introduction</NavigationMenu.Link>
              </li>
              <li>
                <NavigationMenu.Link href="#">Installation</NavigationMenu.Link>
              </li>
              <li>
                <NavigationMenu.Link href="#">Dark mode</NavigationMenu.Link>
              </li>
            </ul>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Trigger>Components</NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <ul className="grid w-80 grid-cols-2">
              {components.map((component) => (
                <li key={component}>
                  <NavigationMenu.Link href="#">
                    {component}
                  </NavigationMenu.Link>
                </li>
              ))}
            </ul>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Link href="#">Blocks</NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
    </NavigationMenu>
  );
}

With descriptions

A NavigationMenu.Link lays out its children in a column, so a title and a short description can sit inside one link.

tsx

import { NavigationMenu } from "@hummingbirdui/react";

const features = [
  {
    title: "Analytics",
    description: "Track traffic, conversions and retention in real time.",
  },
  {
    title: "Automation",
    description: "Trigger workflows from events without writing code.",
  },
  {
    title: "Integrations",
    description: "Connect the tools your team already relies on.",
  },
  {
    title: "Security",
    description: "Single sign-on, audit logs and role-based access.",
  },
];

export default function NavigationMenuDescriptions() {
  return (
    <NavigationMenu>
      <NavigationMenu.List>
        <NavigationMenu.Item>
          <NavigationMenu.Trigger>Product</NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <ul className="grid w-96 grid-cols-2 gap-1">
              {features.map((feature) => (
                <li key={feature.title}>
                  <NavigationMenu.Link href="#">
                    <span className="font-medium">{feature.title}</span>
                    <span className="text-muted">{feature.description}</span>
                  </NavigationMenu.Link>
                </li>
              ))}
            </ul>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Link href="#">Pricing</NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
    </NavigationMenu>
  );
}

Without viewport

Setting viewport={false} renders each panel below its own item with its own surface instead of inside the shared viewport.

tsx

import { NavigationMenu } from "@hummingbirdui/react";

const menus = [
  { title: "Solutions", links: ["Marketing", "Sales", "Support"] },
  { title: "Resources", links: ["Blog", "Guides", "Changelog"] },
];

export default function NavigationMenuNoViewport() {
  return (
    <NavigationMenu viewport={false}>
      <NavigationMenu.List>
        {menus.map((menu) => (
          <NavigationMenu.Item key={menu.title}>
            <NavigationMenu.Trigger>{menu.title}</NavigationMenu.Trigger>
            <NavigationMenu.Content>
              <ul className="w-48">
                {menu.links.map((link) => (
                  <li key={link}>
                    <NavigationMenu.Link href="#">{link}</NavigationMenu.Link>
                  </li>
                ))}
              </ul>
            </NavigationMenu.Content>
          </NavigationMenu.Item>
        ))}
      </NavigationMenu.List>
    </NavigationMenu>
  );
}

The active prop marks the link for the current page, both at the top level and inside a panel.

tsx

import { NavigationMenu } from "@hummingbirdui/react";

export default function NavigationMenuActive() {
  return (
    <NavigationMenu>
      <NavigationMenu.List>
        <NavigationMenu.Item>
          <NavigationMenu.Link href="#" active>
            Overview
          </NavigationMenu.Link>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Trigger>Settings</NavigationMenu.Trigger>
          <NavigationMenu.Content>
            <ul className="w-48">
              <li>
                <NavigationMenu.Link href="#">Profile</NavigationMenu.Link>
              </li>
              <li>
                <NavigationMenu.Link href="#" active>
                  Billing
                </NavigationMenu.Link>
              </li>
              <li>
                <NavigationMenu.Link href="#">
                  Notifications
                </NavigationMenu.Link>
              </li>
            </ul>
          </NavigationMenu.Content>
        </NavigationMenu.Item>
        <NavigationMenu.Item>
          <NavigationMenu.Link href="#">Reports</NavigationMenu.Link>
        </NavigationMenu.Item>
      </NavigationMenu.List>
    </NavigationMenu>
  );
}

Indicator

NavigationMenu.Indicator renders an arrow below the trigger of the open panel.

tsx

import { NavigationMenu } from "@hummingbirdui/react";

const menus = [
  { title: "Products", links: ["Analytics", "Automation", "Reports"] },
  { title: "Company", links: ["About", "Careers", "Press"] },
];

export default function NavigationMenuIndicator() {
  return (
    <NavigationMenu>
      <NavigationMenu.List>
        {menus.map((menu) => (
          <NavigationMenu.Item key={menu.title}>
            <NavigationMenu.Trigger>{menu.title}</NavigationMenu.Trigger>
            <NavigationMenu.Content>
              <ul className="w-48">
                {menu.links.map((link) => (
                  <li key={link}>
                    <NavigationMenu.Link href="#">{link}</NavigationMenu.Link>
                  </li>
                ))}
              </ul>
            </NavigationMenu.Content>
          </NavigationMenu.Item>
        ))}
        <NavigationMenu.Indicator />
      </NavigationMenu.List>
    </NavigationMenu>
  );
}

API Reference

Built on the Radix UI Navigation Menu primitive. Each part forwards all props to its Radix counterpart.

Contains all the parts of a navigation menu.

PropTypeDefault
defaultValuestring—
valuestring—
onValueChangefunction—
delayDurationnumber200
skipDelayDurationnumber300
direnum—
orientationenum"horizontal"
viewportbooleantrue
classNamestring—

Data attributeValues
[data-orientation]"vertical" | "horizontal"
[data-viewport]"true" | "false"

Contains the top level menu items.

PropTypeDefault
asChildbooleanfalse
classNamestring—

Data attributeValues
[data-orientation]"vertical" | "horizontal"

A top level menu item, containing a link or a trigger with its content.

PropTypeDefault
asChildbooleanfalse
valuestring—
classNamestring—

The button that toggles a panel. It renders a trailing chevron that flips while the panel is open.

PropTypeDefault
asChildbooleanfalse
classNamestring—

Data attributeValues
[data-state]"open" | "closed"
[data-disabled]Present when disabled

The panel associated with a trigger.

PropTypeDefault
asChildbooleanfalse
onEscapeKeyDownfunction—
onPointerDownOutsidefunction—
onFocusOutsidefunction—
onInteractOutsidefunction—
forceMountboolean—
classNamestring—

Data attributeValues
[data-state]"open" | "closed"
[data-motion]"to-start" | "to-end" | "from-start" | "from-end"
[data-orientation]"vertical" | "horizontal"

A navigational link. Inside a panel it renders as a menu row; placed directly in an item it matches the triggers.

PropTypeDefault
asChildbooleanfalse
activebooleanfalse
onSelectfunction—
classNamestring—

Data attributeValues
[data-active]Present when active

An optional arrow rendered below the list to highlight the open trigger.

PropTypeDefault
asChildbooleanfalse
forceMountboolean—
classNamestring—

Data attributeValues
[data-state]"visible" | "hidden"
[data-orientation]"vertical" | "horizontal"

The surface that renders the open panel. NavigationMenu adds it automatically unless viewport={false} is set.

PropTypeDefault
asChildbooleanfalse
forceMountboolean—
classNamestring—

Data attributeValues
[data-state]"open" | "closed"
[data-orientation]"vertical" | "horizontal"

CSS VariableDescription
--radix-navigation-menu-viewport-widthThe width of the viewport when visible/hidden, computed from the active content.
--radix-navigation-menu-viewport-heightThe height of the viewport when visible/hidden, computed from the active content.

Styling

Hummingbird React navigation menu is styled through Hummingbird's utility classes and CSS variables. Triggers and top-level links reuse the nav-link class, and panels reuse the dropdown's surface tokens.

  • The navigation-menu, navigation-menu-list, navigation-menu-item, navigation-menu-trigger, navigation-menu-content, navigation-menu-viewport, navigation-menu-link and navigation-menu-indicator classes ship with @hummingbirdui/react.
  • The navigation-menu class defines the --nav-link-* variables for its triggers and the --navigation-menu-* variables (surface, spacing and link colors) for its panels.
  • See the Nav CSS Variables and Dropdown CSS Variables documentation for the underlying tokens.