Skip to main content
Gremorie

Menubar

Desktop-app-style horizontal multi-menu bar. File, Edit, View, Help with submenus, checkboxes, radios, and shortcuts.

Overview

Menubar is the desktop-app menu bar primitive: several menus laid out side by side (File, Edit, View, Help), each opening a dropdown of items, checkboxes, radios, and nested submenus. Built on Radix Menubar, it implements the cross-menu keyboard contract users expect from native menus - once one menu is open, arrow keys walk into the next without re-pressing the trigger.

Reach for Menubar when you are emulating a desktop application - code editors, spreadsheets, image editors, IDEs. For everything else - site nav, single-button action menus, settings panels - use NavigationMenu or DropdownMenu instead.

Preview

Installation

bash npx gremorie@latest add rx-menubar
bash pnpm dlx gremorie@latest add rx-menubar
bash yarn dlx gremorie@latest add rx-menubar
bash bunx --bun gremorie@latest add rx-menubar

Usage

import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarSeparator,
  MenubarShortcut,
  MenubarTrigger,
} from "@gremorie/rx-navigation";

export function AppMenubar() {
  return (
    <Menubar>
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            New file <MenubarShortcut>CtrlN</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>Open...</MenubarItem>
          <MenubarSeparator />
          <MenubarItem>
            Save <MenubarShortcut>CtrlS</MenubarShortcut>
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Undo</MenubarItem>
          <MenubarItem>Redo</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}

Angular edition planned for Phase 5h. Star the repo to track progress.

API

Root container. Wraps Radix Menubar.Root. Renders a horizontal flex h-9 items-center gap-1 rounded-md border bg-background p-1 shadow-xs bar.

PropTypeDefaultDescription
defaultValuestring-Uncontrolled initially open menu.
valuestring-Controlled open menu.
onValueChange(value: string) => void-Fires when the open menu changes.
dir"ltr" | "rtl""ltr"Reading direction.
loopbooleantrueWhether keyboard focus loops at the ends of the bar.

A single menu (trigger plus content). Wraps Radix Menubar.Menu.

PropTypeDefaultDescription
valuestring-Identifier used by Root's controlled value.

The bar button that opens the menu. Wraps Radix Menubar.Trigger.

PropTypeDefaultDescription
disabledbooleanfalseDisables this trigger.

The dropdown panel. Wraps Radix Menubar.Content inside a MenubarPortal.

PropTypeDefaultDescription
align"start" | "center" | "end""start"Horizontal alignment relative to the trigger.
alignOffsetnumber-4Pixel offset from the alignment edge.
sideOffsetnumber8Distance in pixels between the trigger and the content.
side"top" | "right" | "bottom" | "left""bottom"Preferred side.
loopbooleantrueWhether keyboard focus loops at the ends of the panel.

A standard action item.

PropTypeDefaultDescription
insetbooleanfalseWhen true, adds left padding so the item aligns with checkbox / radio items.
variant"default" | "destructive""default""destructive" paints the item and its icon in the destructive token.
disabledbooleanfalseDisables this item.
onSelect(event: Event) => void-Fires on activation. Call event.preventDefault() to keep the menu open.

A toggle item rendered with a leading check indicator.

PropTypeDefaultDescription
checkedboolean | "indeterminate"falseCurrent state.
onCheckedChange(checked: boolean) => void-Fires when the state changes.
disabledbooleanfalseDisables this item.

A radio set rendered with a leading dot indicator.

ComponentPropTypeDescription
RadioGroupvaluestringControlled selected value.
RadioGrouponValueChange(value: string) => voidFires when the selected value changes.
RadioItemvaluestringRequired. Matches the group's value.
RadioItemdisabledbooleanDisables this item.

A non-interactive header inside a content panel.

PropTypeDefaultDescription
insetbooleanfalseWhen true, adds left padding to align with checkbox / radio items.

A 1 px horizontal divider with -mx-1 my-1 to extend across the panel padding.

A right-aligned <span> for the keyboard shortcut hint. Styled with ml-auto text-xs tracking-widest text-muted-foreground.

PropTypeDefaultDescription
...propsReact.ComponentProps<"span">-Standard span attributes.

Nested submenus. The sub-trigger automatically appends a right-pointing chevron.

ComponentPropTypeDescription
SubdefaultOpenbooleanUncontrolled initial open state.
SubopenbooleanControlled open state.
SubonOpenChange(open: boolean) => voidFires when the sub opens or closes.
SubTriggerinsetbooleanAdds left padding for alignment with checkbox / radio items.
SubTriggerdisabledbooleanDisables the sub-trigger.

MenubarGroup is a logical grouping wrapper (no styling); MenubarPortal is the portal Radix uses to mount content (already used internally by <MenubarContent>).

Composition

  1. <Menubar> is the horizontal bar.
  2. Each menu is a <MenubarMenu> containing a <MenubarTrigger> and a <MenubarContent>.
  3. Inside content: a mix of <MenubarItem>, <MenubarCheckboxItem>, <MenubarRadioGroup> (with <MenubarRadioItem> children), <MenubarLabel>, <MenubarSeparator>, and <MenubarSub> for nested menus.
  4. Each action item can carry a trailing <MenubarShortcut> for the keyboard hint.

The cross-menu keyboard contract is what distinguishes Menubar from a row of independent DropdownMenus: once one menu is open, ArrowLeft / ArrowRight walk to the previous / next menu's content directly, without closing and re-opening manually.

Variations

File / Edit / View pattern

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>
        New tab <MenubarShortcut>CtrlT</MenubarShortcut>
      </MenubarItem>
      <MenubarItem>
        New window <MenubarShortcut>CtrlN</MenubarShortcut>
      </MenubarItem>
      <MenubarSeparator />
      <MenubarItem>Print...</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
  <MenubarMenu>
    <MenubarTrigger>Edit</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>
        Undo <MenubarShortcut>CtrlZ</MenubarShortcut>
      </MenubarItem>
      <MenubarItem>
        Redo <MenubarShortcut>CtrlShiftZ</MenubarShortcut>
      </MenubarItem>
    </MenubarContent>
  </MenubarMenu>
  <MenubarMenu>
    <MenubarTrigger>View</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>Reload</MenubarItem>
      <MenubarItem>Fullscreen</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Use as the top-of-window bar in editor-style apps. Each menu groups related commands with shortcuts.

With checkbox and radio items

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>View</MenubarTrigger>
    <MenubarContent>
      <MenubarCheckboxItem checked>Always show bookmarks</MenubarCheckboxItem>
      <MenubarCheckboxItem>Show full URL</MenubarCheckboxItem>
      <MenubarSeparator />
      <MenubarLabel inset>Density</MenubarLabel>
      <MenubarRadioGroup value="comfortable">
        <MenubarRadioItem value="compact">Compact</MenubarRadioItem>
        <MenubarRadioItem value="comfortable">Comfortable</MenubarRadioItem>
        <MenubarRadioItem value="spacious">Spacious</MenubarRadioItem>
      </MenubarRadioGroup>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Use to expose persistent view preferences. Checkbox items toggle independently; radio items pick one of N.

Nested submenus

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>New file</MenubarItem>
      <MenubarSub>
        <MenubarSubTrigger>Open recent</MenubarSubTrigger>
        <MenubarSubContent>
          <MenubarItem>Project alpha</MenubarItem>
          <MenubarItem>Project beta</MenubarItem>
          <MenubarItem>Project gamma</MenubarItem>
        </MenubarSubContent>
      </MenubarSub>
      <MenubarSeparator />
      <MenubarItem variant="destructive">Close window</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Use sparingly. Submenus are valuable for grouping (Open recent, Export as, Theme), but deep nesting hurts discoverability.

Accessibility

  • WAI-ARIA Menubar pattern: the root carries role="menubar"; triggers carry role="menuitem" with aria-haspopup="menu" and aria-expanded; content panels carry role="menu"; items carry role="menuitem", role="menuitemcheckbox", or role="menuitemradio" as appropriate.
  • Cross-menu keyboard: Tab enters the bar; ArrowLeft / ArrowRight walk between triggers (and continue walking between open menus' contents); ArrowDown / Enter open the active trigger; ArrowUp / ArrowDown move between items inside content; ArrowRight opens a sub-trigger; ArrowLeft closes a sub; Escape closes the open menu and returns focus to the trigger.
  • Type-ahead: Radix supports first-letter type-ahead inside each menu, so typing "S" jumps to the first item starting with S.
  • Shortcut hints: <MenubarShortcut> is visual only. Wire the actual keyboard bindings outside the component (your own document-level handler).
  • Disabled items: render with aria-disabled="true" and are skipped by arrow-key navigation.

On this page