Menubar
Provides a persistent set of application commands.
import * as stylex from "@stylexjs/stylex"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
const styles = stylex.create({ menubar: { width: "18rem" }, content: { width: "11rem" } })
export default function MenubarDemo() {
return (
<Menubar xstyle={styles.menubar}>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarGroup>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarGroup>
</MenubarSubContent>
</MenubarSub>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>
Print... <MenubarShortcut>⌘P</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarGroup>
<MenubarItem>Search the web</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarGroup>
</MenubarSubContent>
</MenubarSub>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent xstyle={styles.content}>
<MenubarGroup>
<MenubarCheckboxItem>Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem defaultChecked>Full URLs</MenubarCheckboxItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>
Reload <MenubarShortcut>⌘R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>Hide Sidebar</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup defaultValue="benoit">
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>Edit...</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}Installation
Install the following dependencies:
pnpm add @base-ui/react @stylexjs/stylexnpm install @base-ui/react @stylexjs/stylexyarn add @base-ui/react @stylexjs/stylexbun add @base-ui/react @stylexjs/stylexSet up the token layer and the StyleX compiler from Installation, including states.ts, state-styles.ts, and interop.css for icons. Install Dropdown Menu for the shared menu parts.
Copy and paste the following code into your project.
"use client"
import type { ComponentProps, CSSProperties } from "react"
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"
import { Menu as MenuPrimitive } from "@base-ui/react/menu"
import * as stylex from "@stylexjs/stylex"
import type { StyleXStyles } from "@stylexjs/stylex"
import { stateStyles } from "@/lib/state-styles"
import { states } from "@/theme/states"
import { color, duration, ease, leading, radius, space, text } from "@/theme/tokens.stylex"
import {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuContent,
} from "@/components/ui/dropdown-menu"
const styles = stylex.create({
root: {
display: "flex",
alignItems: "center",
height: space.s8,
gap: space.s0_5,
borderRadius: radius.lg,
padding: "3px",
},
vertical: { flexDirection: "column", height: "auto", alignItems: "stretch" },
trigger: {
display: "flex",
alignItems: "center",
userSelect: "none",
borderRadius: `calc(${radius.lg} - 3px)`,
borderWidth: 1,
borderStyle: "solid",
paddingInline: space.s1_5,
paddingBlock: space.s0_5,
fontSize: text.sm,
lineHeight: leading.sm,
fontWeight: 500,
backgroundColor: { default: color.transparent, ":hover": color.itemHighlightBg },
transitionProperty: "color, background-color, border-color, box-shadow",
transitionDuration: duration.color,
transitionTimingFunction: ease.standard,
},
open: {
color: color.secondaryForeground,
},
content: {
minWidth: "9rem",
},
})
const menubarStyles = {
...styles,
root: [states.well, styles.root],
open: [states.key, states.keySurface, states.wellKey, styles.open],
focusRing: states.focusRing,
}
type Overrides = { xstyle?: StyleXStyles; className?: string; style?: CSSProperties }
function Menubar({
xstyle,
className,
style,
...props
}: Omit<MenubarPrimitive.Props, "className" | "style"> & Overrides) {
return (
<MenubarPrimitive
data-slot="menubar"
{...props}
{...stateStyles(
(state) => [
states.well,
styles.root,
state.orientation === "vertical" && styles.vertical,
xstyle,
],
{ className, style },
)}
/>
)
}
function MenubarTrigger({
xstyle,
className,
style,
...props
}: Omit<MenuPrimitive.Trigger.Props, "className" | "style"> & Overrides) {
return (
<MenuPrimitive.Trigger
data-slot="menubar-trigger"
{...props}
{...stateStyles(
(state) => [
styles.trigger,
states.focusRing,
state.open && [states.key, states.keySurface, states.wellKey, styles.open],
state.disabled && states.disabled,
xstyle,
],
{ className, style },
)}
/>
)
}
function MenubarContent({
xstyle,
alignOffset = -4,
sideOffset = 8,
...props
}: ComponentProps<typeof DropdownMenuContent>) {
return (
<DropdownMenuContent
data-slot="menubar-content"
alignOffset={alignOffset}
sideOffset={sideOffset}
xstyle={[styles.content, xstyle]}
{...props}
/>
)
}
function MenubarMenu(props: ComponentProps<typeof DropdownMenu>) {
return <DropdownMenu {...props} />
}
function MenubarPortal(props: ComponentProps<typeof DropdownMenuPortal>) {
return <DropdownMenuPortal {...props} />
}
function MenubarGroup(props: ComponentProps<typeof DropdownMenuGroup>) {
return <DropdownMenuGroup data-slot="menubar-group" {...props} />
}
function MenubarItem(props: ComponentProps<typeof DropdownMenuItem>) {
return <DropdownMenuItem data-slot="menubar-item" {...props} />
}
function MenubarLabel(props: ComponentProps<typeof DropdownMenuLabel>) {
return <DropdownMenuLabel data-slot="menubar-label" {...props} />
}
function MenubarSeparator(props: ComponentProps<typeof DropdownMenuSeparator>) {
return <DropdownMenuSeparator data-slot="menubar-separator" {...props} />
}
function MenubarShortcut(props: ComponentProps<typeof DropdownMenuShortcut>) {
return <DropdownMenuShortcut data-slot="menubar-shortcut" {...props} />
}
function MenubarCheckboxItem(props: ComponentProps<typeof DropdownMenuCheckboxItem>) {
return <DropdownMenuCheckboxItem data-slot="menubar-checkbox-item" {...props} />
}
function MenubarRadioGroup(props: ComponentProps<typeof DropdownMenuRadioGroup>) {
return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />
}
function MenubarRadioItem(props: ComponentProps<typeof DropdownMenuRadioItem>) {
return <DropdownMenuRadioItem data-slot="menubar-radio-item" {...props} />
}
function MenubarSub(props: ComponentProps<typeof DropdownMenuSub>) {
return <DropdownMenuSub {...props} />
}
function MenubarSubTrigger(props: ComponentProps<typeof DropdownMenuSubTrigger>) {
return <DropdownMenuSubTrigger data-slot="menubar-sub-trigger" {...props} />
}
function MenubarSubContent(props: ComponentProps<typeof DropdownMenuSubContent>) {
return <DropdownMenuSubContent data-slot="menubar-sub-content" {...props} />
}
export {
Menubar,
MenubarTrigger,
MenubarContent,
MenubarMenu,
MenubarPortal,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarSeparator,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
menubarStyles,
}Update the import paths to match your project setup.
Usage
import {
Menubar,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from "@/components/ui/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>RTL support is not included.
Composition
Use the following composition to build a Menubar:
Menubar
├── MenubarMenu
│ ├── MenubarTrigger
│ └── MenubarContent
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarItem
│ │ └── MenubarItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ ├── MenubarCheckboxItem
│ │ └── MenubarCheckboxItem
│ ├── MenubarSeparator
│ ├── MenubarGroup
│ │ ├── MenubarLabel
│ │ └── MenubarRadioGroup
│ │ ├── MenubarRadioItem
│ │ └── MenubarRadioItem
│ └── MenubarSub
│ ├── MenubarSubTrigger
│ └── MenubarSubContent
│ └── MenubarGroup
│ ├── MenubarLabel
│ ├── MenubarItem
│ └── MenubarItem
└── MenubarMenu
├── MenubarTrigger
└── MenubarContent
└── MenubarGroup
├── MenubarLabel
├── MenubarItem
└── MenubarItemCheckbox
Use MenubarCheckboxItem for toggleable options.
import * as stylex from "@stylexjs/stylex"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from "@/components/ui/menubar"
const styles = stylex.create({ menubar: { width: "18rem" }, content: { width: "16rem" } })
export default function MenubarCheckbox() {
return (
<Menubar xstyle={styles.menubar}>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent xstyle={styles.content}>
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem defaultChecked>Always Show Full URLs</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>⌘R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Format</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem defaultChecked>Strikethrough</MenubarCheckboxItem>
<MenubarCheckboxItem>Code</MenubarCheckboxItem>
<MenubarCheckboxItem>Superscript</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}Radio
Use MenubarRadioGroup and MenubarRadioItem for single-select options.
"use client"
import * as React from "react"
import * as stylex from "@stylexjs/stylex"
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarTrigger,
} from "@/components/ui/menubar"
const styles = stylex.create({ menubar: { width: "18rem" } })
export default function MenubarRadio() {
const [user, setUser] = React.useState("benoit")
const [theme, setTheme] = React.useState("system")
return (
<Menubar xstyle={styles.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>
<MenubarMenu>
<MenubarTrigger>Theme</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={theme} onValueChange={setTheme}>
<MenubarRadioItem value="light">Light</MenubarRadioItem>
<MenubarRadioItem value="dark">Dark</MenubarRadioItem>
<MenubarRadioItem value="system">System</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}Submenu
Use MenubarSub, MenubarSubTrigger, and MenubarSubContent for nested menus.
import * as stylex from "@stylexjs/stylex"
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
const styles = stylex.create({ menubar: { width: "18rem" } })
export default function MenubarSubmenu() {
return (
<Menubar xstyle={styles.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>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}With icons
Add icons to MenubarItem.
import * as stylex from "@stylexjs/stylex"
import {
FileIcon,
FolderIcon,
HelpCircleIcon,
SaveIcon,
SettingsIcon,
TrashIcon,
} from "lucide-react"
import {
Menubar,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarTrigger,
} from "@/components/ui/menubar"
const styles = stylex.create({ menubar: { width: "18rem" } })
export default function MenubarIcons() {
return (
<Menubar xstyle={styles.menubar}>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<FileIcon />
New File <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<FolderIcon />
Open Folder
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
<SaveIcon />
Save <MenubarShortcut>⌘S</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>More</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
<SettingsIcon />
Settings
</MenubarItem>
<MenubarItem>
<HelpCircleIcon />
Help
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<TrashIcon />
Delete
</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}API reference
Menubar accepts Base UI Menubar props. Menu parts accept their Dropdown Menu props, including render. Styled parts take xstyle, className and inline style.
MenubarContent
| Prop | Type | Default |
|---|---|---|
side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "bottom" |
sideOffset | number | 8 |
align | "start" | "center" | "end" | "start" |
alignOffset | number | -4 |
menubarStyles
| Key | What it is |
|---|---|
root | The well and trigger spacing |
vertical | Vertical arrangement |
trigger | Trigger type, nested corner, row-highlight hover |
open | The raised key while the menu is open |
content | Menu minimum width |
focusRing | Shared keyboard focus ring |
Data attributes
| Attribute | On | Effect |
|---|---|---|
data-slot="menubar" | root | Bar anatomy |
data-orientation | root | Horizontal or vertical |
data-open, data-disabled | trigger | Base UI state |
data-highlighted, data-disabled, data-checked | items | Base UI state |
data-inset, data-variant | items | Item appearance |
data-side, data-starting-style, data-ending-style | popup | Placement and transition state |
data-slot="menubar-trigger" | trigger | Part anatomy |
data-slot="menubar-content" | content | Part anatomy |
data-slot="menubar-group" | group | Part anatomy |
data-slot="menubar-item" | item | Part anatomy |
data-slot="menubar-label" | label | Part anatomy |
data-slot="menubar-separator" | separator | Part anatomy |
data-slot="menubar-shortcut" | shortcut | Part anatomy |
data-slot="menubar-checkbox-item" | checkbox-item | Part anatomy |
data-slot="menubar-radio-group" | radio-group | Part anatomy |
data-slot="menubar-radio-item" | radio-item | Part anatomy |
data-slot="menubar-sub-trigger" | sub-trigger | Part anatomy |
data-slot="menubar-sub-content" | sub-content | Part anatomy |
Checkbox and radio indicator spans retain their dropdown-menu-*-item-indicator slots. MenubarMenu, MenubarSub and MenubarPortal add no element.