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
<Menubar>
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.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | string | - | Uncontrolled initially open menu. |
value | string | - | Controlled open menu. |
onValueChange | (value: string) => void | - | Fires when the open menu changes. |
dir | "ltr" | "rtl" | "ltr" | Reading direction. |
loop | boolean | true | Whether keyboard focus loops at the ends of the bar. |
<MenubarMenu>
A single menu (trigger plus content). Wraps Radix Menubar.Menu.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Identifier used by Root's controlled value. |
<MenubarTrigger>
The bar button that opens the menu. Wraps Radix Menubar.Trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables this trigger. |
<MenubarContent>
The dropdown panel. Wraps Radix Menubar.Content inside a MenubarPortal.
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "start" | Horizontal alignment relative to the trigger. |
alignOffset | number | -4 | Pixel offset from the alignment edge. |
sideOffset | number | 8 | Distance in pixels between the trigger and the content. |
side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side. |
loop | boolean | true | Whether keyboard focus loops at the ends of the panel. |
<MenubarItem>
A standard action item.
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | When 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. |
disabled | boolean | false | Disables this item. |
onSelect | (event: Event) => void | - | Fires on activation. Call event.preventDefault() to keep the menu open. |
<MenubarCheckboxItem>
A toggle item rendered with a leading check indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | "indeterminate" | false | Current state. |
onCheckedChange | (checked: boolean) => void | - | Fires when the state changes. |
disabled | boolean | false | Disables this item. |
<MenubarRadioGroup> and <MenubarRadioItem>
A radio set rendered with a leading dot indicator.
| Component | Prop | Type | Description |
|---|---|---|---|
RadioGroup | value | string | Controlled selected value. |
RadioGroup | onValueChange | (value: string) => void | Fires when the selected value changes. |
RadioItem | value | string | Required. Matches the group's value. |
RadioItem | disabled | boolean | Disables this item. |
<MenubarLabel>
A non-interactive header inside a content panel.
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | When true, adds left padding to align with checkbox / radio items. |
<MenubarSeparator>
A 1 px horizontal divider with -mx-1 my-1 to extend across the panel padding.
<MenubarShortcut>
A right-aligned <span> for the keyboard shortcut hint. Styled with ml-auto text-xs tracking-widest text-muted-foreground.
| Prop | Type | Default | Description |
|---|---|---|---|
...props | React.ComponentProps<"span"> | - | Standard span attributes. |
<MenubarSub>, <MenubarSubTrigger>, <MenubarSubContent>
Nested submenus. The sub-trigger automatically appends a right-pointing chevron.
| Component | Prop | Type | Description |
|---|---|---|---|
Sub | defaultOpen | boolean | Uncontrolled initial open state. |
Sub | open | boolean | Controlled open state. |
Sub | onOpenChange | (open: boolean) => void | Fires when the sub opens or closes. |
SubTrigger | inset | boolean | Adds left padding for alignment with checkbox / radio items. |
SubTrigger | disabled | boolean | Disables the sub-trigger. |
<MenubarGroup> and <MenubarPortal>
MenubarGroup is a logical grouping wrapper (no styling); MenubarPortal is the portal Radix uses to mount content (already used internally by <MenubarContent>).
Composition
<Menubar>is the horizontal bar.- Each menu is a
<MenubarMenu>containing a<MenubarTrigger>and a<MenubarContent>. - Inside content: a mix of
<MenubarItem>,<MenubarCheckboxItem>,<MenubarRadioGroup>(with<MenubarRadioItem>children),<MenubarLabel>,<MenubarSeparator>, and<MenubarSub>for nested menus. - 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 carryrole="menuitem"witharia-haspopup="menu"andaria-expanded; content panels carryrole="menu"; items carryrole="menuitem",role="menuitemcheckbox", orrole="menuitemradio"as appropriate. - Cross-menu keyboard:
Tabenters the bar;ArrowLeft/ArrowRightwalk between triggers (and continue walking between open menus' contents);ArrowDown/Enteropen the active trigger;ArrowUp/ArrowDownmove between items inside content;ArrowRightopens a sub-trigger;ArrowLeftcloses a sub;Escapecloses 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.
Related
- DropdownMenu - single-trigger action menu.
- NavigationMenu - marketing-site primary nav.
- ContextMenu - right-click action menu.