DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Menubar

A desktop-app style horizontal menu bar (File / Edit / View / Help) built on the Base UI Menubar primitive — one tab stop, roving focus, hover-switch, submenus, and the full menu-item family shared with DropdownMenu.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  Menubar,
  MenubarMenu,
  MenubarTrigger,
  MenubarContent,
  MenubarItem,
  MenubarSeparator,
  MenubarCheckboxItem,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSub,
  MenubarSubTrigger,
  MenubarSubContent,
  MenubarLabel,
  MenubarShortcut,
} from "@tessinaui/ui";
<Menubar>
  <MenubarMenu value="file">
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem shortcut={["mod", "N"]}>New tab</MenubarItem>
      <MenubarItem shortcut={["mod", "shift", "N"]}>New window</MenubarItem>
      <MenubarSeparator />
      <MenubarItem shortcut={["mod", "P"]}>Print…</MenubarItem>
    </MenubarContent>
  </MenubarMenu>

  <MenubarMenu value="edit">
    <MenubarTrigger>Edit</MenubarTrigger>
    <MenubarContent>
      <MenubarItem shortcut={["mod", "Z"]}>Undo</MenubarItem>
      <MenubarItem shortcut={["mod", "shift", "Z"]}>Redo</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Examples

Default

A horizontal menu bar with File, Edit, and View menus, each opening a dropdown of commands. shortcut={["mod", "N"]} resolves ⌘N on Apple keyboards and Ctrl+N everywhere else.

Variants

Four bar treatments — solid, outline, ghost, and floating.

Sizes

Five sizes from xs to xl scale triggers, padding, and dropdown content.

On color

tone="on-color" adapts the bar to a colored or dark plate via the surface ink contract. Inside a <Surface> the tone is inherited automatically — no prop needed.

Composition

The hybrid editor-bar pattern: put the shared chrome on a wrapper row, keep the Menubar itself ghost inside it, and place interactive extras (undo/redo buttons) beside the menubar — a role="menubar" may own only menu items, so a plain button inside it is an axe-critical violation. Give the extras the bar's radius (IconButton defaults to rounded="full") so every hit target on the strip hovers with the same corner; dividers are aria-hidden. Chevron-suffixed triggers are trailingIcon.

Icon triggers

Triggers take leadingIcon / trailingIcon; an icon-only trigger must carry an aria-label.

Item features

Items support leading icons, platform-resolving shortcuts, a badge slot, descriptions, a destructive error intent, and nested submenus.

Checkbox & radio items

Toggle options with MenubarCheckboxItem and single-select groups with MenubarRadioGroup. Both accept the full item anatomy — icons, shortcuts, descriptions.

Switch items

MenubarSwitchItem paints a toggle row as a switch — the spreadsheet / settings-menu idiom. Semantically it stays menuitemcheckbox, so the menu remains open on toggle.

Link items

MenubarLinkItem renders a real <a> — middle-click, ⌘-click, and the status-bar URL preview keep working.

Header & footer

MenubarHeader / MenubarFooter pin bands above and below the items — an account block, a "create new" row. The band itself is role="group", so an action inside it should be a real MenubarItem rather than a bare button: a menu row has to keep the family's hover fill, radius and row height, and a menuitem inside that group is legal ARIA.

Groups

Titled sections with MenubarGroup + MenubarLabel; the label names the group for assistive tech.

Disabled

Whole-bar disabled, per-trigger disabled, and per-item disabled. Unavailable actions dim — they are never hidden, so the menu keeps teaching what the app can do, and disabled items stay focusable for screen-reader discovery.

RTL

dir="rtl" flips arrow-key order, submenu sides, and chevrons. Left unset, the bar inherits the document direction.

Controlled

Control the open menu with value / onValueChange (null closes all).

Modal

modal locks page scroll while a menu is open; MenubarBackdrop adds an opt-in scrim. The house default is non-modal.

Loading

MenubarSkeleton mirrors the bar chrome while menu data loads.

When to use

Reach for Menubar when building desktop-style application shells — editors, design tools, IDE-like products. The pattern users expect: a horizontal strip of top-level triggers (File, Edit, View, Help), each opening a vertical dropdown with commands, shortcuts, separators, submenus.

  • Website navigation (logo, nav items, mega-menus) → NavigationMenu
  • A single dropdown on a button → DropdownMenu
  • Right-click menus → ContextMenu
  • Searchable command list → Command — never embed a search field inside a menu
  • A button that also opens a menu → SplitButton

Menu-writing guidance (HIG-derived): keep trigger titles to one word; append … to any item that needs more input before completing; group related items with separators; give icons to all items in a group or none; show/hide actions flip their label ("Show sidebar" ↔ "Hide sidebar") while persistent attributes use checkmarks; keep submenus one level deep and consider a new menu beyond ~5 nested items; dim unavailable items instead of removing them.

Behaviour

  • Single open at a time — opening one menu closes any other
  • Hover-switch — a closed bar never opens on hover; once any menu is open, hovering a sibling trigger switches to it (classic menubar UX, owned by the Base UI primitive)
  • One tab stop — the whole bar is a single composite widget with a roving tabindex; ArrowLeft / ArrowRight move between triggers (RTL flips), loop wraps
  • Type-ahead — type a letter to focus the matching item inside an open menu
  • Escape closes — and returns focus to the trigger
  • On narrow viewports the bar scrolls horizontally; popups clamp to the viewport width. Labels that truncate should get a tooltip with the full text.

Accessibility

Implements the WAI-ARIA menubar pattern: role="menubar" on the bar, each trigger is a menuitem with aria-haspopup="menu" + aria-expanded, popups are role="menu", and items are menuitem / menuitemcheckbox / menuitemradio with aria-checked. Disabled items keep aria-disabled and stay focusable so they remain discoverable.

Keyboard

KeyContextResult
Tab / Shift+TabanywhereEnters/leaves the bar as one tab stop; with a menu open, closes it and moves on
ArrowRight / ArrowLefton a triggerPrevious/next trigger (flipped in RTL; wraps per loop)
Enter / Space / ArrowDownon a triggerOpens the menu, focus moves to the first item
ArrowDown / ArrowUpin a menuNext / previous item
Home / Endon a triggerFirst / last trigger
Home / Endin a menuFirst / last item
ArrowRighton a submenu triggerOpens the submenu (flipped in RTL)
ArrowLeftin a submenuCloses it, focus returns to the parent item
ArrowRight / ArrowLefton a leaf itemCloses this menu and opens the next / previous menubar menu
Enter / Spaceon an itemActivates it; the menu closes (checkbox/radio/switch rows stay open)
Escin a menuCloses it, focus returns to the trigger
printable characterin a menuType-ahead to the matching item

Screen readers & AT matrix

Trigger names come from their label text — icon-only triggers must pass aria-label. MenubarShortcut keycaps carry spoken key names, so ["mod", "K"] announces "Command K" rather than a glyph. Item description text is part of the row's content, not a separate announcement.

ATStatus
VoiceOver + Safari (macOS)Pending — smoke pass scheduled with this release
NVDA + Chrome (Windows)UNTESTED — no Windows host available
JAWSUNTESTED — no Windows host available

The component stays beta until at least one VoiceOver and one NVDA pass are recorded.

API Reference

Menubar props

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Scales triggers, padding, gap, and dropdown content
variant"solid" | "outline" | "ghost" | "floating""solid"Bar visual treatment — solid (filled), outline (bordered), ghost (no chrome), floating (bordered + shadow)
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius — cascades to triggers, popups (full caps at the panel radius), and items
tone"default" | "on-color"inheritedon-color adapts the bar to a colored/dark plate; defaults to the nearest Surface's tone
dir"ltr" | "rtl"inheritedExplicit reading direction. Unset, the bar follows the document; set, it also drives Base UI's keyboard order and submenu sides
valuestring | null—Controlled open menu value
defaultValuestring | nullnullUncontrolled initial open menu value
onValueChange(value: string | null) => void—Called when the open menu changes
loopbooleantrueWrap arrow-key focus from last to first trigger
modalbooleanfalseLock page scroll and outside interaction while a menu is open
disabledbooleanfalseDisable the whole bar — triggers dim but stay visible
intent"none" | "primary""none"Deprecated — renders unchanged for one minor, then removed

MenubarMenu props

PropTypeDescription
valuestringUnique identifier for this menu — used to control open state
childrenReactNodeA MenubarTrigger followed by a MenubarContent

MenubarTrigger props

PropTypeDescription
leadingIconReactNodeIcon before the label (sized per bar size)
trailingIconReactNodeIcon after the label — e.g. a chevron
disabledbooleanDisables this trigger

Renders a <button role="menuitem">. Accepts all standard button HTML attributes. Icon-only triggers must pass aria-label.

MenubarContent props

PropTypeDefaultDescription
sizeMenubarSizeinheritedOverride dropdown size — defaults to the bar's size
width"narrow" | "default" | "wide""default"Constrain dropdown width
side"top" | "bottom" | "left" | "right""bottom"Anchor side
align"start" | "center" | "end""start"Anchor alignment
sideOffsetnumber6Pixel offset from the trigger
alignOffsetnumber0Alignment offset
collisionPaddingnumber8Minimum gap from viewport edges before flipping/shifting
positionerClassNamestring—Extra classes on the positioner (e.g. raise z-50)
portal / containerboolean / HTMLElementtrue / document.bodyPortal control for previews and embedded shells
inertboolean—Static, non-interactive popup for illustrations

Popups scroll when taller than the available space and never exceed the viewport width. Corner radius follows the bar's rounded.

MenubarItem props

PropTypeDescription
insetbooleanReserve space for an indicator on the start side
leadingIconReactNodeIcon before the label
trailingIconReactNodeIcon after the label
shortcutstring | string[]Keyboard shortcut hint. Prefer the array form — ["mod", "K"] resolves ⌘K / Ctrl+K per platform. A literal string ("⌘K") still works
badgeReactNodeStatus pill beside the label — "New", "Beta", a count. Pass a <Badge>
descriptionstringSecondary line below the label
intent"none" | "error"error styles the item as destructive
disabledbooleanDisables the item (stays visible and focusable)
closeOnClickbooleanWhether activating closes the menu. Default true
onClick(event) => voidCalled when activated

MenubarLinkItem props

All MenubarItem anatomy props on a real <a> — href, target, rel are native. closeOnClick defaults to false: a plain click navigates (the unload closes the menu) while modified clicks open a tab and leave it open. SPA route changes that do not unload should pass closeOnClick explicitly.

MenubarCheckboxItem / MenubarRadioItem props

checked + onCheckedChange (checkbox) or group value + onValueChange with per-item value (radio), plus the full item anatomy — leadingIcon, trailingIcon, shortcut, badge, description. Both default closeOnClick to false, so multi-toggling keeps the menu open.

MenubarSwitchItem props

A MenubarCheckboxItem painted as a switch (leadingIcon, description, checked, onCheckedChange). The switch is presentation only — role, keyboard, and announcements are the checkbox item's.

MenubarHeader / MenubarFooter props

Non-interactive bands pinned above/below the items (sticky and separator props, both true by default). Rendered as role="group" — the only div shape legal inside role="menu".

MenubarSub / MenubarSubTrigger / MenubarSubContent

Composed exactly like the parent — MenubarSub wraps the pair, MenubarSubTrigger shows a chevron (auto-flipped in RTL) and accepts inset + leadingIcon, MenubarSubContent opens to the logical side.

MenubarLabel, MenubarSeparator, MenubarShortcut, MenubarGroup, MenubarBackdrop

Layout primitives — MenubarLabel for section headings (associates with its MenubarGroup via aria-labelledby), MenubarSeparator for hairlines, MenubarShortcut for inline shortcut keycaps (also auto-rendered by the shortcut prop; accepts string | string[], glyphs, platform), MenubarGroup for grouping, MenubarBackdrop for the opt-in scrim under a modal bar.

Notes

  • Built on the Base UI Menubar primitive plus Menu parts: roving tabindex, hover-switch, arrow navigation, and type-ahead are the primitive's own behavior, not re-implemented here.
  • The popup family (content, items, labels, header/footer, skeleton internals) is DropdownMenu's — one implementation, two names — so the two menus can never drift apart. Anything documented for DropdownMenu items applies verbatim.
  • Each MenubarMenu is a controlled Menu.Root driven by the bar's open value; lift value/onValueChange to fully control the bar.
  • data-slot attributes (menubar, menubar-trigger, menu-content, menu-item, …) are stamped on every part for testing and agent targeting.
LinkMeter

On this page

PlaygroundInstallationUsageExamplesDefaultVariantsSizesOn colorCompositionIcon triggersItem featuresCheckbox & radio itemsSwitch itemsLink itemsHeader & footerGroupsDisabledRTLControlledModalLoadingWhen to useBehaviourAccessibilityKeyboardScreen readers & AT matrixAPI ReferenceMenubar propsMenubarMenu propsMenubarTrigger propsMenubarContent propsMenubarItem propsMenubarLinkItem propsMenubarCheckboxItem / MenubarRadioItem propsMenubarSwitchItem propsMenubarHeader / MenubarFooter propsMenubarSub / MenubarSubTrigger / MenubarSubContentMenubarLabel, MenubarSeparator, MenubarShortcut, MenubarGroup, MenubarBackdropNotes