Menubar
A visually persistent menu common in desktop applications.
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
$ bext ui add menubar
Copy and paste the following into src/components/ui/menubar.tsx.
This component is interactive. Make sure the shared island is loaded (see Islands). It reacts to data-ui="menubar" hooks.
Usage
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
Menubar
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Bar h-9 rounded-md border bg-background p-1 shadow-xs, flex items-center gap-1. |
MenubarMenu
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Relative wrapper for trigger + content. |
MenubarTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
| for | string | — | Matches the id of the Content. Renders a <button role="menuitem" aria-haspopup="menu" aria-expanded>. |
MenubarContent
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | — | Matches Trigger.for. |
| align | "start" | "end" | "center" | "start" | Horizontal alignment of the panel. |
MenubarItem
| 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. |
MenubarCheckboxItem / RadioItem
| 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. |
MenubarSub / SubTrigger / SubContent
| 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.
Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom: fine-grained reactivity, no virtual DOM.
On This Page