bx

Menubar

A visually persistent menu common in desktop applications.

tsx
<Menubar>
  <MenubarMenu>
    <MenubarTrigger for="file">File</MenubarTrigger>
    <MenubarContent id="file">
      <MenubarItem>New Tab <MenubarShortcut>⌘T</MenubarShortcut></MenubarItem>
      <MenubarSeparator />
      <MenubarItem>Print...</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Menubar is a horizontal menu bar in the style of a desktop app (File / Edit / View / …). Each MenubarMenu opens its menu on click, or from the keyboard with Enter, Space or ArrowDown on its trigger. The ui.js island handles the arrow keys between menus and inside them.

Same composition as DropdownMenu (Items, Label, Separator, Shortcut, Sub), laid out as a horizontal row at the top of a page or app.

Installation

Terminal
$ bext ui add menubar

Usage

tsx
import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarShortcut, MenubarSeparator } from "@bext-stack/ui/primitives"

<Menubar>
  <MenubarMenu>
    <MenubarTrigger for="file">File</MenubarTrigger>
    <MenubarContent id="file">
      <MenubarItem>New Tab <MenubarShortcut>⌘T</MenubarShortcut></MenubarItem>
      <MenubarItem>New Window</MenubarItem>
      <MenubarSeparator />
      <MenubarItem>Print...</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Anatomy

<Menubar>
  ├── <MenubarMenu>
  │   ├── <MenubarTrigger for="file-menu">File</MenubarTrigger>
  │   └── <MenubarContent id="file-menu">
  │       ├── <MenubarItem>New Tab <MenubarShortcut>⌘T</MenubarShortcut></MenubarItem>
  │       └── <MenubarItem>Print... <MenubarShortcut>⌘P</MenubarShortcut></MenubarItem>
  └── <MenubarMenu>...</MenubarMenu>

API Reference

Prop Type Default Description
className string — Bar h-9 rounded-md border bg-background p-1 shadow-xs, flex items-center gap-1.
Prop Type Default Description
children ReactNode — Relative wrapper for trigger + content.
Prop Type Default Description
for string — Matches the id of the Content. Renders a <button role="menuitem" aria-haspopup="menu" aria-expanded>.
Prop Type Default Description
id string — Matches Trigger.for.
align "start" | "end" | "center" "start" Horizontal alignment of the panel.
Prop Type Default Description
href string — Renders an <a> when set.
inset boolean — pl-8, to align with checkbox/radio items.
disabled boolean — Sets data-disabled and aria-disabled="true": no pointer events, 50% opacity, skipped by the arrow keys.
variant "default" | "destructive" "default" Destructive → text-destructive.
Prop Type Default Description
checked boolean — Initial state. Check mark / radio dot shown when true; activating the item toggles a checkbox item and selects a radio item within its MenubarRadioGroup.
Prop Type Default Description
— — — Submenu, revealed on hover or with ArrowRight / Enter on the SubTrigger.

Data Attributes

Attributes you can target in custom CSS to override the style for each state.

Attribute On Values Description
data-ui="toggle-pop" Trigger data-target="#id" Opens on click.
data-state Trigger "open" | "closed" Set by the island together with aria-expanded. Styles the open trigger (bg-accent).
data-overlay data-light Content hidden Light popover: an outside click closes it.
data-ui="close" Item — Closes the menu on activation and returns focus to its trigger.
data-state CheckboxItem, RadioItem "checked" | "unchecked" Starts from the checked prop. The island updates it, with aria-checked and the indicator, when the item is activated.

Keyboard

Key Action
Tab The menubar is a single tab stop: Tab enters on one trigger and leaves the bar. In an open menu, Tab closes it.
ArrowRight / ArrowLeft On a trigger: moves to the next or previous trigger, wrapping at the ends. In an open menu: closes it and opens the next or previous menu on its first item. On a SubTrigger, ArrowRight opens the submenu; in a submenu, ArrowLeft closes it.
Home / End On a trigger: first or last trigger. In a menu: first or last item.
Enter / Space On a trigger: opens the menu and focuses the first item. On an item: activates it, closes the menu and returns focus to the trigger.
ArrowDown / ArrowUp On a trigger: opens the menu on the first or the last item. In a menu: next or previous item, wrapping at the ends. Disabled items are skipped.
A letter In a menu: moves to the next item whose label starts with that letter.
Escape Closes the open menu (or the submenu that has focus) and returns focus to its trigger.

Accessibility

Menubar has role="menubar"; each MenubarMenu wraps a single trigger + content pair. A trigger is a <button role="menuitem" aria-haspopup="menu"> whose aria-expanded the island keeps in sync on every open and close, including a close by outside click or Escape. MenubarContent has role="menu" and is named by its trigger.

Items have role="menuitem", menuitemcheckbox or menuitemradio (with aria-checked) and tabindex="-1"; MenubarSeparator has role="separator", MenubarRadioGroup has role="group", and MenubarLabel is plain text that is never focused.

Focus: the triggers use a roving tabindex, so the bar takes one tab stop and the arrow keys move inside it. Opened from the keyboard, a menu focuses its first item; opened with the pointer, the menu itself takes focus. Menus open on click, not on hover.

Menubar has no prop for an accessible name. When a page has more than one menubar, wrap each one in a labelled <nav> or region.

Try it live
Open in play

Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.

src/app/page.tsx 2 files · signals · runs in your browser
Pure PRISM + real bext signals: fine-grained, no re-render.