Components

Menubar

A desktop-style application menu bar — a persistent horizontal set of dropdown menus for organizing commands, shortcuts, and toggles.

When to use it

Menubar is for editor-shaped screens — documents, spreadsheets, anything that needs a fixed, exhaustive grouping of commands within one workspace. It is not the primary navigation of an app: moving between areas of a Comitor product is the job of Sidebar and AppHeader from @comitor/ui/shell. If a menu here would take the user to a different page rather than act on the current one, it belongs in the shell instead.

Basic usage

Compose MenubarMenu blocks inside a Menubar. Open state is managed by Radix, so no useState is required.

Checkbox & radio items

Use MenubarCheckboxItem for toggles and MenubarRadioGroup for single-choice options.

Both are controlled only. The Radix menu primitives read checked (defaulting to false) and value; unlike the standalone Checkbox and RadioGroup they have no defaultChecked or defaultValue. Passing one type-checks — React allows those attributes on any element — but it lands on the underlying <div> and the row never ticks. The preview below holds its own state and passes onCheckedChange / onValueChange. (The defaultValue in the Menubar root table below is a different prop and does work — it belongs to the bar itself, not to a radio group.)

Submenus & destructive items

Nest MenubarSub for cascading menus, and pass variant="destructive" to flag dangerous actions.

The package renders MenubarSubContent inside its own portal. MenubarContent carries overflow-hidden to keep the panel clipped to its rounded corners, and its open animation leaves a transform behind, which makes the panel a containing block even for position: fixed children. Without the portal a submenu opens beside its parent and is clipped away — the chevron row reacts to nothing, and nothing is logged.

Compact density

Menu rows measure their vertical padding with py-(--menu-item-py) rather than a fixed py-1.5, so a long command list tightens from 6px to 4px per row under data-density="compact" — the same axis that tightens table rows and sidebar items. Only the vertical axis moves. In an app the attribute is set once on <html> by DensityProvider from @comitor/ui/shell; the panel below scopes it by hand so you can compare it with the menus above.

Props

The parts exported from @comitor/ui. Every part forwards the props of the Radix primitive it wraps, plus the native props of the element it renders.

PropTypeDefaultDescription
MenubarMenubarPrimitive.RootThe horizontal bar itself — h-9, gap-1, rounded border on the page background. Manages roving focus between menus.
MenubarMenuMenubarPrimitive.MenuWraps one trigger and its content. Accepts an optional value to address it from the root.
MenubarTriggerMenubarPrimitive.TriggerThe button in the bar. Tints to the accent token while its menu is open.
MenubarContentMenubarPrimitive.ContentThe floating panel of items, portalled to document.body.
MenubarPortalMenubarPrimitive.PortalEscape hatch for portalling content yourself — Content and SubContent already portal, so you rarely need it.
MenubarGroupMenubarPrimitive.GroupGroups related items so assistive tech announces them together.
MenubarItemMenubarPrimitive.ItemA selectable command row. See the item props below.
MenubarCheckboxItemMenubarPrimitive.CheckboxItemToggle row with a check indicator. Controlled only — checked (default false) plus onCheckedChange; there is no defaultChecked.
MenubarRadioGroupMenubarPrimitive.RadioGroupGroups radio items and tracks the selected value. Controlled only — value plus onValueChange; there is no defaultValue.
MenubarRadioItemMenubarPrimitive.RadioItemSingle-choice row with a dot indicator; requires a unique value.
MenubarLabelMenubarPrimitive.LabelNon-interactive section heading. Accepts inset to align with item text.
MenubarSeparatorMenubarPrimitive.SeparatorFull-bleed divider drawn with the decorative border token.
MenubarShortcutspanMuted, right-aligned shortcut text. Use KeyboardHint from the same entry when the combo contains a modifier.
MenubarSubMenubarPrimitive.SubWraps a nested submenu (SubTrigger + SubContent).
MenubarSubTriggerMenubarPrimitive.SubTriggerRow that opens a submenu on hover or arrow key; accepts inset and appends a chevron.
MenubarSubContentMenubarPrimitive.SubContentThe submenu panel, portalled so the parent panel cannot clip it.

Menubar (root)

PropTypeDefaultDescription
valuestringControlled value of the open menu, matching a MenubarMenu value.
defaultValuestring''Which menu is open on first render, for uncontrolled usage.
onValueChange(value: string) => voidCalled when a different menu opens or all of them close.
loopbooleantrueWhether arrow-key focus wraps from the last trigger back to the first.
dir'ltr' | 'rtl''ltr'Reading direction, affecting arrow keys and submenu placement.

MenubarContent

PropTypeDefaultDescription
align'start' | 'center' | 'end''start'Alignment against the trigger. The package overrides the Radix default of center.
alignOffsetnumber-4Offset along the alignment axis — pulls the panel left so its padding lines up with the trigger label.
sideOffsetnumber8Gap in pixels between the bar and the panel.
loopbooleanfalseWhether keyboard navigation wraps from the last item back to the first.
classNamestringAdditional Tailwind classes. The panel is min-w-[12rem] by default.

MenubarItem

PropTypeDefaultDescription
variant'default' | 'destructive''default'Styles the row — destructive paints the label, icon and focus tint in the destructive ink token.
insetbooleanfalseAdds left padding to align with items that have a leading icon or indicator.
disabledbooleanfalseDisables the item, removing pointer events and dimming it.
onSelect(event: Event) => voidCalled when the item is selected via pointer or keyboard.
textValuestringText used for typeahead matching when the item contains more than plain text.
asChildbooleanfalseMerges the item props onto its single child — use it to render a link as a menu item.

Accessibility

  • Built on Radix Menubar, which implements the WAI-ARIA menubar pattern with the correct roles and relationships.
  • Full keyboard support: / move between menus, / move between items, and Esc closes the open menu.
  • Type-ahead lets users jump to an item by typing its label, and focus is trapped within the open menu until it closes.
  • Disabled items set data-disabled, removing pointer events and skipping them in keyboard navigation.
  • variant="destructive" paints the label in the destructive ink step, not the destructive fill. The fill is a button background pinned to one value across both themes and only reaches 3.53:1 as text on the popover surface — worse on focus, where the row tints to bg-destructive/20 (2.98:1). The ink step clears both themes and both backgrounds: 7.15:1 / 6.18:1 in light at rest and on focus, 6.42:1 / 5.42:1 in dark.
  • Shortcut glyphs typed into MenubarShortcut are shown verbatim, so a hard-coded is wrong for every non-Mac user. Use KeyboardHint from @comitor/ui with keys="mod+b" to resolve the modifier per platform; it is aria-hidden, so name the shortcut in the item's accessible name when it matters.