Menubar
A desktop-style horizontal menu bar with top-level menus, built on Radix UI Menubar.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a Menubar:
Menubar
├── MenubarMenu
│ ├── MenubarTrigger
│ └── MenubarContent
│ ├── MenubarGroup (optional)
│ │ ├── MenubarLabel (optional)
│ │ └── MenubarItem / MenubarCheckboxItem / MenubarRadioGroup / …
│ ├── MenubarSeparator (optional)
│ └── MenubarSub (optional)
│ ├── MenubarSubTrigger
│ └── MenubarSubContent
│ └── MenubarItem / …
└── MenubarMenu
├── MenubarTrigger
└── MenubarContentEach MenubarMenu wraps one top-level MenubarTrigger and its MenubarContent. Commands and groups live inside MenubarContent (or MenubarSubContent for nested menus). Behavior follows Radix UI Menubar.
Usage
import {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
} from '@by/experience-system';Menubar and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>New</MenubarItem>
<MenubarItem>Open</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>Wrap the tree in ThemeProvider so dir on the root matches your app (or pass dir explicitly on Menubar). MenubarContent already portals into the ThemeProvider container.
When to use
- Desktop-style application menus (File, Edit, View) where one menu stays visible and panels open from the bar.
- Dense command surfaces that should remain discoverable without a separate trigger control.
When not to use
- Primary site navigation or marketing headers—prefer Navigation Menu or visible links.
- Mobile-first patterns where a horizontal bar does not fit—use Dropdown Menu, Drawer, or a compact pattern instead.
Examples
The live preview at the top of this page uses menubar-usage (two top-level menus with grouped items).
Basic
Use MenubarGroup and MenubarLabel to structure sections inside a menu.
Submenus
Use MenubarSub, MenubarSubTrigger, and MenubarSubContent for nested rows.
Checkboxes
Use MenubarCheckboxItem for toggleable options. Pair optional hints with MenubarShortcut.
Radio groups
Use MenubarRadioGroup and MenubarRadioItem for single-select options.
Shortcuts
Add MenubarShortcut as the last child of an item so it aligns to the trailing edge.
API Reference
Subsection titles name @by/experience-system exports and the Radix part they wrap. Pieces map to Radix UI Menubar and the underlying Radix Menu content primitives. Styling, density-aware spacing, and icons are provided by @by/experience-system and @by/icons. The tables mirror upstream props, data attributes, and CSS variables; MenubarContent omits onEntryFocus from its TypeScript surface the same way Radix does.
Menubar
Root. Contains all top-level MenubarMenu parts. Exported as Menubar; forwards MenubarPrimitive.Root props from radix-ui. dir defaults from ThemeProvider and can be overridden via props.
| Prop | Type | Default |
|---|---|---|
value | string | No default value |
defaultValue | string | No default value |
onValueChange | function | No default value |
loop | boolean | false |
dir | enum | From ThemeProvider |
size | enum | "md" (experience system: "sm" | "md" | "lg" — outline rail and trigger sizing) |
MenubarMenu
Associates a MenubarTrigger with MenubarContent.
| Prop | Type | Default |
|---|---|---|
value | string | No default value |
MenubarTrigger
The control that toggles a menu. Uses ghost Button styling sized from Menubar context.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
MenubarPortal
Portals children into the document. In this experience system, container defaults to the element from useContainerElement() (ThemeProvider) instead of document.body.
| Prop | Type | Default |
|---|---|---|
forceMount | boolean | No default value |
container | HTMLElement | From ThemeProvider (see above) |
MenubarContent
The surface that opens from a top-level trigger. This export wraps Portal with the Experience System container so you typically render MenubarContent directly (without a separate MenubarPortal for the main menu).
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
loop | boolean | false |
onOpenAutoFocus | function | No default value |
onDismiss | function | No default value |
disableOutsidePointerEvents | boolean | No default value |
disableOutsideScroll | boolean | false |
trapFocus | boolean | false |
onCloseAutoFocus | function | No default value |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
side | enum | "bottom" |
sideOffset | number | 0 |
align | enum | "start" |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | [] |
collisionPadding | number | Padding | 0 |
arrowPadding | number | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
updatePositionStrategy | enum | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-menubar-content-transform-origin | The transform-origin computed from the content and trigger positions |
--radix-menubar-content-available-width | The remaining width between the trigger and the boundary edge |
--radix-menubar-content-available-height | The remaining height between the trigger and the boundary edge |
--radix-menubar-trigger-width | The width of the trigger |
--radix-menubar-trigger-height | The height of the trigger |
MenubarGroup
Groups multiple items for semantics and optional labeling.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
MenubarLabel
A non-focusable label for a section. Optional inset aligns with inset items.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
inset | boolean | No default value (experience system) |
MenubarItem
A selectable command in the menu.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
inset | boolean | No default value (experience system: aligns text with icon rows) |
color | enum | "neutral" ("neutral" | "error" — use error for irreversible actions) |
| Data attribute | Values |
|---|---|
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-color] | "neutral" | "error" |
MenubarCheckboxItem
A toggle row backed by a checkbox. Uses a Check indicator internally.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
checked | boolean | 'indeterminate' | No default value |
onCheckedChange | function | No default value |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "checked" | "unchecked" | "indeterminate" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
MenubarRadioGroup
Groups MenubarRadioItem components.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | No default value |
onValueChange | function | No default value |
MenubarRadioItem
A single choice in a radio group. Indicator uses ShapeBulletCircleFill.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | No default value (required) |
disabled | boolean | No default value |
onSelect | function | No default value |
textValue | string | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
MenubarSeparator
A visual divider between groups.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
MenubarShortcut
Design-system helper: a span for keyboard hints, aligned to the trailing edge when placed inside an item. Not a Radix primitive part.
MenubarSub
Contains all parts of a submenu.
| Prop | Type | Default |
|---|---|---|
open | boolean | No default value |
defaultOpen | boolean | No default value |
onOpenChange | function | No default value |
MenubarSubTrigger
Opens a nested menu. Includes a trailing ArrowChevronRight icon.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
textValue | string | No default value |
inset | boolean | No default value (experience system) |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
MenubarSubContent
The panel for a submenu. Render inside MenubarSub.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
loop | boolean | false |
onOpenAutoFocus | function | No default value |
onDismiss | function | No default value |
disableOutsidePointerEvents | boolean | No default value |
disableOutsideScroll | boolean | false |
trapFocus | boolean | false |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
onFocusOutside | function | No default value |
onInteractOutside | function | No default value |
forceMount | boolean | No default value |
sideOffset | number | 0 |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | [] |
collisionPadding | number | Padding | 0 |
arrowPadding | number | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
updatePositionStrategy | enum | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-menubar-content-transform-origin | The transform-origin computed for submenu content |
@by/experience-system does not export MenubarArrow or MenubarItemIndicator; checkbox and radio indicators are built into the respective items. For Radix’s optional arrow API, see the upstream docs.
Accessibility
Uses roving tabindex among items. Prefer MenubarLabel (optionally inset) for section titles, and keep top-level MenubarTrigger labels clear when menus are collapsed.
Keyboard interactions
| Key | Description |
|---|---|
Space / Enter | Opens the focused top-level menu or activates the focused item. |
ArrowDown / ArrowUp | Moves focus between items when a menu is open. |
ArrowRight / ArrowLeft | Moves between top-level menus when the bar is focused; when focus is on MenubarSubTrigger, opens or closes the submenu (respects reading direction). |
Esc | Closes the open menu. |
For the full primitive reference and upstream updates, see Radix UI — Menubar.
Source in the repo: packages/experience-system/src/components/Menubar/Menubar.tsx. Agent-oriented contracts: packages/experience-system/src/components/Menubar/Menubar.instructions.md.