Experience System
Components

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-system

In 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
    └── MenubarContent

Each 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.

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.

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.

PropTypeDefault
valuestringNo default value
defaultValuestringNo default value
onValueChangefunctionNo default value
loopbooleanfalse
direnumFrom ThemeProvider
sizeenum"md" (experience system: "sm" | "md" | "lg" — outline rail and trigger sizing)

Associates a MenubarTrigger with MenubarContent.

PropTypeDefault
valuestringNo default value

The control that toggles a menu. Uses ghost Button styling sized from Menubar context.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
Data attributeValues
[data-state]"open" | "closed"

Portals children into the document. In this experience system, container defaults to the element from useContainerElement() (ThemeProvider) instead of document.body.

PropTypeDefault
forceMountbooleanNo default value
containerHTMLElementFrom ThemeProvider (see above)

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).

PropTypeDefault
asChildbooleanfalse
loopbooleanfalse
onOpenAutoFocusfunctionNo default value
onDismissfunctionNo default value
disableOutsidePointerEventsbooleanNo default value
disableOutsideScrollbooleanfalse
trapFocusbooleanfalse
onCloseAutoFocusfunctionNo default value
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
sideenum"bottom"
sideOffsetnumber0
alignenum"start"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionBoundaryBoundary[]
collisionPaddingnumber | Padding0
arrowPaddingnumber0
stickyenum"partial"
hideWhenDetachedbooleanfalse
updatePositionStrategyenumNo default value
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS variableDescription
--radix-menubar-content-transform-originThe transform-origin computed from the content and trigger positions
--radix-menubar-content-available-widthThe remaining width between the trigger and the boundary edge
--radix-menubar-content-available-heightThe remaining height between the trigger and the boundary edge
--radix-menubar-trigger-widthThe width of the trigger
--radix-menubar-trigger-heightThe height of the trigger

Groups multiple items for semantics and optional labeling.

PropTypeDefault
asChildbooleanfalse

A non-focusable label for a section. Optional inset aligns with inset items.

PropTypeDefault
asChildbooleanfalse
insetbooleanNo default value (experience system)

A selectable command in the menu.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
insetbooleanNo default value (experience system: aligns text with icon rows)
colorenum"neutral" ("neutral" | "error" — use error for irreversible actions)
Data attributeValues
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-color]"neutral" | "error"

A toggle row backed by a checkbox. Uses a Check indicator internally.

PropTypeDefault
asChildbooleanfalse
checkedboolean | 'indeterminate'No default value
onCheckedChangefunctionNo default value
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
Data attributeValues
[data-state]"checked" | "unchecked" | "indeterminate"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

Groups MenubarRadioItem components.

PropTypeDefault
asChildbooleanfalse
valuestringNo default value
onValueChangefunctionNo default value

A single choice in a radio group. Indicator uses ShapeBulletCircleFill.

PropTypeDefault
asChildbooleanfalse
valuestringNo default value (required)
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value
Data attributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

A visual divider between groups.

PropTypeDefault
asChildbooleanfalse

Design-system helper: a span for keyboard hints, aligned to the trailing edge when placed inside an item. Not a Radix primitive part.

Contains all parts of a submenu.

PropTypeDefault
openbooleanNo default value
defaultOpenbooleanNo default value
onOpenChangefunctionNo default value

Opens a nested menu. Includes a trailing ArrowChevronRight icon.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
textValuestringNo default value
insetbooleanNo default value (experience system)
Data attributeValues
[data-state]"open" | "closed"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

The panel for a submenu. Render inside MenubarSub.

PropTypeDefault
asChildbooleanfalse
loopbooleanfalse
onOpenAutoFocusfunctionNo default value
onDismissfunctionNo default value
disableOutsidePointerEventsbooleanNo default value
disableOutsideScrollbooleanfalse
trapFocusbooleanfalse
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
sideOffsetnumber0
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionBoundaryBoundary[]
collisionPaddingnumber | Padding0
arrowPaddingnumber0
stickyenum"partial"
hideWhenDetachedbooleanfalse
updatePositionStrategyenumNo default value
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS variableDescription
--radix-menubar-content-transform-originThe 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

KeyDescription
Space / EnterOpens the focused top-level menu or activates the focused item.
ArrowDown / ArrowUpMoves focus between items when a menu is open.
ArrowRight / ArrowLeftMoves between top-level menus when the bar is focused; when focus is on MenubarSubTrigger, opens or closes the submenu (respects reading direction).
EscCloses 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.