Components
A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands.
Playground
import {
Menubar,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from "@workspace/ui/components/menubar"
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>New Window</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>Share</MenubarItem>
<MenubarItem>Print</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>checked and update it in onCheckedChange. Plain items sharing a menu with checkable ones take inset so their labels align with the indicator column.const [fullUrls, setFullUrls] = React.useState(true)
<Menubar>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent className="w-64">
<MenubarCheckboxItem checked={fullUrls} onCheckedChange={setFullUrls}>
Always Show Full URLs
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>⌘R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>value; the item matching it shows the indicator.const [user, setUser] = React.useState("benoit")
<Menubar>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={user} onValueChange={setUser}>
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Edit...</MenubarItem>
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar><MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>⌘P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>variant="destructive" for irreversible actions: label and icon turn destructive, and the focus highlight follows.import { FileIcon, FloppyDiskIcon, FolderIcon } from "@phosphor-icons/react"
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<FileIcon />
New File <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<FolderIcon />
Open Folder
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
<FloppyDiskIcon />
Save <MenubarShortcut>⌘S</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>inset to align with the indicator column of checkbox and radio items in the same menu.import { MenubarLabel } from "@workspace/ui/components/menubar"
<MenubarContent>
<MenubarLabel inset>Panels</MenubarLabel>
<MenubarItem inset>Show Sidebar</MenubarItem>
<MenubarItem inset>Show Terminal</MenubarItem>
</MenubarContent>dir prop (or a surrounding Direction provider) — a plain dir="rtl" ancestor is not enough for the keyboard navigation to flip. Styling follows via logical properties: shortcuts sit at the far end with ms-auto, indicators at the start, and the submenu caret rotates via rtl:rotate-180.<Menubar dir="rtl">
<MenubarMenu>
<MenubarTrigger>ملف</MenubarTrigger>
<MenubarContent>
<MenubarItem>
علامة تبويب جديدة <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>نافذة جديدة</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>مشاركة</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>رابط البريد الإلكتروني</MenubarItem>
<MenubarItem>الرسائل</MenubarItem>
</MenubarSubContent>
</MenubarSub>
</MenubarContent>
</MenubarMenu>
</Menubar>role="menubar" with roving focus: Arrow keys move across triggers and between items, Enter, Space or Arrow Down open a menu, typing jumps to a matching item, and Escape closes and returns focus to the trigger. Contents are portaled with role="menu", and checkable items expose their state via aria-checked on menuitemcheckbox / menuitemradio roles.<div role="menubar"> <button role="menuitem" aria-haspopup="menu" aria-expanded="true">File</button> <button role="menuitem" aria-haspopup="menu">Edit</button> </div> <div role="menu" aria-orientation="vertical"> <!-- portaled --> <div role="menuitem">New Tab</div> <div role="menuitemcheckbox" aria-checked="true">Full URLs</div> <div role="menuitemradio" aria-checked="false">Andy</div> <div role="separator" aria-orientation="horizontal"></div> <div role="menuitem" aria-haspopup="menu">Share</div> </div>
| Component | Prop | Default |
|---|---|---|
| Menubar | dir?: "ltr" | "rtl" | inherited |
| Menubar | loop?: boolean | false |
| MenubarContent | align?: "start" | "center" | "end" | "start" |
| MenubarContent | sideOffset?: number | 8 |
| MenubarContent | alignOffset?: number | -4 |
| MenubarItem | variant?: "default" | "destructive" | "default" |
| MenubarItem | inset?: boolean | false |
| MenubarItem | disabled?: boolean | false |
| MenubarItem | onSelect?: (event: Event) => void | — |
| MenubarCheckboxItem | checked?: boolean | "indeterminate" | — |
| MenubarCheckboxItem | onCheckedChange?: (checked: boolean) => void | — |
| MenubarRadioGroup | value?: string, onValueChange?: (value: string) => void | — |
| MenubarRadioItem | value: string | required |
| MenubarLabel | inset?: boolean | false |
| MenubarSubTrigger | inset?: boolean | false |
<Menubar data-slot="menubar" /> // role="menubar" <MenubarMenu data-slot="menubar-menu" /> // no DOM node <MenubarTrigger data-slot="menubar-trigger" /> // <button role="menuitem"> <MenubarContent data-slot="menubar-content" /> // role="menu", portaled <MenubarGroup data-slot="menubar-group" /> // role="group" <MenubarItem data-slot="menubar-item" /> // role="menuitem" <MenubarCheckboxItem data-slot="menubar-checkbox-item" /> // role="menuitemcheckbox" <MenubarRadioGroup data-slot="menubar-radio-group" /> // role="group" <MenubarRadioItem data-slot="menubar-radio-item" /> // role="menuitemradio" <MenubarLabel data-slot="menubar-label" /> <MenubarSeparator data-slot="menubar-separator" /> // role="separator" <MenubarShortcut data-slot="menubar-shortcut" /> // <span> <MenubarSub data-slot="menubar-sub" /> // no DOM node <MenubarSubTrigger data-slot="menubar-sub-trigger" /> // role="menuitem" + caret <MenubarSubContent data-slot="menubar-sub-content" /> // role="menu"
Tokens used
14 design tokens consumed by the Menubar component.
--popover
Popover
--popover-foreground
Popover Foreground
--muted
Muted
--accent
Accent
--accent-foreground
Accent Foreground
--destructive
Destructive
--muted-foreground
Muted Foreground
--foreground
Foreground
--border
Border
--radius-md
Radius 0.8× (8px)
--radius-sm
Radius 0.6× (6px)
--text-sm
Text Small (14px)
--text-xs
Text Extra Small (12px)
--spacing
Spacing unit (4px)