Menubar

Desktop-app style menu bar (File / Edit / View). Horizontal menu strip with nested dropdowns.

Installation

pnpm
pnpm dlx @hex-core/cli add menubar

View menu with grouped sections

Use MenubarLabel + MenubarGroup to cluster related items with section headings — a clean alternative when checkbox items aren't exposed

tsx
<Menubar>
  <MenubarMenu>
    <MenubarTrigger>View</MenubarTrigger>
    <MenubarContent className="w-56">
      <MenubarLabel>Layout</MenubarLabel>
      <MenubarGroup>
        <MenubarItem inset onSelect={() => toggleStatusbar()}>Toggle status bar</MenubarItem>
        <MenubarItem inset onSelect={() => toggleSidebar()}>Toggle sidebar</MenubarItem>
      </MenubarGroup>
      <MenubarSeparator />
      <MenubarLabel>Zoom</MenubarLabel>
      <MenubarGroup>
        <MenubarItem onSelect={() => zoomIn()}>
          Zoom in<MenubarShortcut>⌘=</MenubarShortcut>
        </MenubarItem>
        <MenubarItem onSelect={() => zoomOut()}>
          Zoom out<MenubarShortcut>⌘-</MenubarShortcut>
        </MenubarItem>
        <MenubarItem onSelect={() => resetZoom()}>
          Reset<MenubarShortcut>⌘0</MenubarShortcut>
        </MenubarItem>
      </MenubarGroup>
      <MenubarSeparator />
      <MenubarItem onSelect={() => enterFullscreen()}>
        Enter fullscreen<MenubarShortcut>⌃⌘F</MenubarShortcut>
      </MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Disabled items when signed out

Mark items unavailable until the user signs in by passing disabled to MenubarItem — keeps discovery while making the gating obvious

tsx
const isSignedIn = useSession()?.user != null;

return (
  <Menubar>
    <MenubarMenu>
      <MenubarTrigger>File</MenubarTrigger>
      <MenubarContent>
        <MenubarItem disabled={!isSignedIn} onSelect={() => newProject()}>
          New project<MenubarShortcut>⌘N</MenubarShortcut>
        </MenubarItem>
        <MenubarItem disabled={!isSignedIn} onSelect={() => openProject()}>
          Open…<MenubarShortcut>⌘O</MenubarShortcut>
        </MenubarItem>
        <MenubarSeparator />
        <MenubarItem disabled={!isSignedIn} onSelect={() => exportData()}>
          Export data…
        </MenubarItem>
        <MenubarSeparator />
        <MenubarItem onSelect={() => openHelp()}>Help</MenubarItem>
      </MenubarContent>
    </MenubarMenu>
  </Menubar>
);

API Reference

PropTypeDefaultDescription
value
stringControlled open menu id
defaultValue
stringDefault open menu for uncontrolled usage
onValueChange
functionCallback when open menu changes
loop
booleantrueWhen true, arrow-key navigation wraps
dir
"ltr" | "rtl"Reading direction
className
stringAdditional CSS classes on the Menubar root

AI Guidance

When to use

Use for desktop-app shell menus: editors, IDEs, creative tools. Provides persistent menu bar with keyboard shortcuts.

When not to use

Don't use for website navigation (use NavigationMenu). Don't use for single-button menus (use DropdownMenu). Poor fit for mobile — consider a hamburger menu.

Common mistakes

  • Using for website navigation (user expectations don't match)
  • Missing shortcuts (expected affordance in menubar UX)
  • Deeply nested sub-menus (>2 levels feels labyrinthine)

Accessibility

Full WAI-ARIA menubar pattern: arrow keys navigate menus, Enter/Space opens, Escape closes. Radix handles roles and state.

Token budget: 700