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
'use client';import { Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarTrigger,} from '@gremorie/rx-navigation';export function MenubarPreview() { return ( <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>View</MenubarTrigger> <MenubarContent> <MenubarCheckboxItem checked> Always show bookmarks </MenubarCheckboxItem> <MenubarCheckboxItem>Show full URL</MenubarCheckboxItem> <MenubarSeparator /> <MenubarRadioGroup value="comfortable"> <MenubarRadioItem value="compact">Compact</MenubarRadioItem> <MenubarRadioItem value="comfortable">Comfortable</MenubarRadioItem> </MenubarRadioGroup> </MenubarContent> </MenubarMenu> </Menubar> );}Anatomy
Menubar
└─ MenubarMenu one top-level menu (File, Edit, …)
├─ MenubarTrigger clickable label that opens the menu
└─ MenubarContent portaled dropdown panel
├─ MenubarItem command row (inset / destructive variants)
│ └─ MenubarShortcut right-aligned keyboard hint
├─ MenubarCheckboxItem toggleable item with a check indicator
├─ MenubarRadioGroup single-choice group
│ └─ MenubarRadioItem one radio option with a dot indicator
├─ MenubarLabel non-interactive group header
├─ MenubarSeparator 1px divider between groups
└─ MenubarSub nested submenu
├─ MenubarSubTrigger row that opens the submenu
└─ MenubarSubContent the submenu panelInstallation
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>
);
}The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.
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 with shortcuts
'use client';import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarShortcut, MenubarTrigger,} from '@gremorie/rx-navigation';export function MenubarShortcutsPreview() { return ( <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, and <MenubarShortcut> renders the keyboard hint right-aligned. The shortcut text is visual only - wire the real bindings in your own handler.
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
'use client';import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger,} from '@gremorie/rx-navigation';export function MenubarSubmenuPreview() { return ( <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. <MenubarSubTrigger> appends a right-pointing chevron automatically.
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.