Navigation Menu

Header flyouts and expandable sidebar navigation with up to three levels.

NavigationMenu supports two patterns: header links with Radix flyouts, and an expandable sidebar with up to three navigation levels. The docs app uses the sidebar variant inside AppShell.

Installation

import {
  NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger,
  NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport,
} from "@iammikz/srui";

Usage

Sidebar navigation

Use variant="sidebar" with nested items. A root group can contain links or subgroups; subgroups contain links, giving three levels at most.

<NavigationMenu
  variant="sidebar"
  aria-label="Application"
  activeHref="/projects/workspace/overview"
  items={[
    { id: "dashboard", label: "Dashboard", href: "/" },
    {
      id: "projects", label: "Projects",
      children: [{
        id: "workspace", label: "Workspace",
        children: [
          { id: "overview", label: "Overview", href: "/projects/workspace/overview" },
          { id: "team", label: "Team", href: "/projects/workspace/team" },
        ],
      }],
    },
  ]}
/>
Sidebar propDescription
itemsNavigation data; each item needs a stable id and a label
activeHrefExact current URL; the matching link gets aria-current="page" and its ancestors open
renderLinkOptional (item, anchorProps) => ReactNode for router integration; pass the supplied props to your link
aria-labelAccessible navigation label; defaults to Navigation

Items support optional icon content. Groups accept defaultOpen; links accept disabled. Branches toggle independently with click, Enter, or Space. When activeHref changes, the matching ancestors reopen; users can still collapse the active branch manually. Use unique IDs among siblings.

With React Router, normalize a trailing slash before passing activeHref if your item URLs omit it:

<NavigationMenu
  variant="sidebar"
  items={items}
  activeHref={pathname.replace(/\/$/, "") || "/"}
  renderLink={(item, { href, ...props }) => <Link {...props} to={item.href} />}
/>

Sidebar mode uses inline nested lists, so it needs no viewport, indicator, or header primitives. It uses the --sidebar* tokens across all presets. Exported types include NavigationMenuProps, NavigationMenuSidebarItem, NavigationMenuSidebarGroup, NavigationMenuSidebarLink, and NavigationMenuSidebarLinkProps.

Exports

ExportNotes
NavigationMenu / NavigationMenuList / NavigationMenuItemRoot/list/item
NavigationMenuTriggerOpens the item's content; chevron rotates when open
NavigationMenuContentThe flyout markup — composes any layout (grids, lists)
NavigationMenuLinkStyled link; active prop for the current page
NavigationMenuIndicator / NavigationMenuViewportThe active dot + the shared animated panel — render once per menu

Accessibility

Header mode uses the Radix keyboard model: tab to a trigger, arrows between items, Enter or arrow-down opens content, Escape closes.

Sidebar mode uses ordinary navigation links and disclosure buttons with aria-expanded and aria-controls. Tab moves between visible controls; Enter or Space expands groups. Collapsed links are hidden from focus and assistive technology. The current page carries aria-current="page".