A desktop-style bar of menus that stays open as you move across it.
Example
import {
Menubar, MenubarMenu, MenubarItem, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem,
MenubarLabel, MenubarGroup, MenubarSeparator, MenubarShortcut, MenubarSub,
} from "@elements/ui/menubar";
<Menubar>
<MenubarMenu label="File">
<MenubarItem>New Tab <MenubarShortcut>⌘T</MenubarShortcut></MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSeparator />
<MenubarSub label="Share">
<MenubarItem>Email link</MenubarItem>
</MenubarSub>
</MenubarMenu>
<MenubarMenu label="View">
<MenubarCheckboxItem checked={view.bookmarks}>Always Show Bookmarks Bar</MenubarCheckboxItem>
</MenubarMenu>
<MenubarMenu label="Profiles">
<MenubarRadioGroup value={view.profile}>
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarMenu>
</Menubar>
Parts
| Part |
What it is |
Menubar |
The bar (role=menubar). Owns value, the open menu's value. |
MenubarMenu |
One menu: renders its trigger (role=menuitem, label or the trigger slot) and, while open, its role=menu content with the default slot's items. |
MenubarItem |
An action row (role=menuitem). With href it renders a link. Selecting it closes the menu unless onSelect calls preventDefault() or closeOnSelect is false. |
MenubarCheckboxItem |
A row that toggles (role=menuitemcheckbox). checked is two-way and may be "indeterminate". |
MenubarRadioGroup |
Groups radio items (role=group); value is two-way and holds the checked item's value. |
MenubarRadioItem |
One choice in a radio group (role=menuitemradio). |
MenubarLabel |
A heading row. Skipped by arrow keys; names the group it sits in. |
MenubarGroup |
Groups items (role=group). |
MenubarSeparator |
A rule between groups (role=separator). |
MenubarShortcut |
A keyboard hint at the end of an item. |
MenubarSub |
A submenu: renders its trigger row (label or the trigger slot) and, while open, its own floating menu with the default slot's items. |
MenubarInput |
A filter row at the top of the menu: typing hides the rows that do not match. Focus stays in the input while ArrowDown and ArrowUp move the highlight; Enter chooses. |
MenubarClear |
A button that empties the filter input. clearButton on the input renders one in its row. |
MenubarEmpty |
A row shown only when the filter matches nothing. Its default slot is the message. |
Slots
| Slot |
Part |
Content |
default |
Menubar |
The MenubarMenu elements. |
trigger |
MenubarMenu |
Rich trigger content (an icon and text); label is the plain form. |
default |
MenubarMenu |
The menu's items. |
default |
MenubarItem, MenubarCheckboxItem, MenubarRadioItem |
The row: text, an <Icon> (sized from --icon-size), a Shortcut. |
indicator |
MenubarCheckboxItem, MenubarRadioItem |
Replaces the check (or dot) shown while checked. Put the checked glyph in an element with class is-checked, the indeterminate one in is-indeterminate. |
trigger |
MenubarSub |
Rich content for the trigger row (an icon and text); label is the plain-text form. |
default |
MenubarSub |
The submenu's items. |
Keyboard
| Keys |
Action |
Tab |
Moves focus to the bar's one tab stop: the last trigger used, or the first. |
ArrowRight / ArrowLeft |
On a trigger: the next or previous trigger (wraps unless loop is false). Inside an open menu (not on a sub trigger): opens the next or previous menu on its first item. |
Enter / Space / ArrowDown |
On a trigger: opens its menu on the first item. |
ArrowUp |
On a trigger: opens its menu on the last item. |
ArrowDown / ArrowUp (vertical bar) |
On a trigger: the next or previous trigger. |
ArrowRight (vertical bar) |
On a trigger: opens its menu beside the bar on the first item. ArrowLeft in the menu goes back to the trigger. Flipped under rtl. |
A letter on a trigger |
Focuses the next trigger that starts with it. |
ArrowDown / ArrowUp |
Next or previous item; from the menu itself, the first or last. Wraps with loop. |
Home / PageUp |
First item. |
End / PageDown |
Last item. |
Enter / Space |
Chooses the item (toggles a checkbox, checks a radio) and closes the menu; on a sub trigger, opens the submenu and focuses its first item. |
ArrowRight (ArrowLeft in rtl) |
On a sub trigger, opens the submenu and focuses its first item. |
ArrowLeft (ArrowRight in rtl) |
In a submenu, closes it and focuses its trigger. |
A letter |
Typeahead: focuses the next enabled item that starts with the typed text. |
Escape |
Closes the whole menu and returns focus. |
Tab / Shift + Tab |
Closes the menu and moves focus on, as from the trigger. |
ArrowDown / ArrowUp, Enter (in a filter input) |
Move the highlight while focus stays in the input; Enter chooses the highlighted row. |
Data attributes
| Attribute |
Values |
On |
Description |
data-orientation |
"horizontal" | "vertical" |
Menubar |
|
data-has-submenu-open |
present |
Menubar |
While any of its menus is open. |
data-state |
"open" | "closed" |
trigger (.menubar-trigger) |
|
data-pressed |
present |
trigger (.menubar-trigger) |
While the menu a press of the trigger opened is open. |
data-modal |
present |
bar (.menubar) |
When modal. |
data-highlighted |
present |
trigger |
While focused. |
data-disabled |
present |
trigger |
|
data-state |
"open" | "closed" |
.menubar-content |
|
data-side |
"top" | "right" | "bottom" | "left" |
.menubar-content |
The side it opened on, after flipping. |
data-align |
"start" | "center" | "end" |
.menubar-content |
|
data-orientation |
"vertical" |
.menubar-content, items |
|
data-highlighted |
present |
MenubarItem, MenubarCheckboxItem, MenubarRadioItem, sub trigger |
The focused row, by keyboard or pointer. |
data-disabled |
present |
items |
|
data-variant |
"default" | "destructive" |
MenubarItem |
|
data-state |
"checked" | "unchecked" | "indeterminate" |
MenubarCheckboxItem, MenubarRadioItem, .menu-item-indicator |
|
data-state |
"open" | "closed" |
sub trigger (.menu-sub-trigger) |
|
data-orientation |
"horizontal" | "vertical" |
MenubarSeparator |
|
hidden |
present |
items, groups, separators, empty row |
Set by the filter on rows that do not match. |
data-instant |
"click" | "dismiss" | "group" |
.menubar-content, arrow, backdrop |
click when the keyboard opened it, dismiss when Escape closed it, group when a menubar swapped menus; a theme may skip the animation. |
--positioner-width / --positioner-height |
px |
.menubar-content |
The content places itself, so these are its own size while open. |
data-highlighted |
present |
MenubarInput |
While the input shows focus; cleared when the keyboard highlights a row. |
data-starting-style, data-ending-style |
present |
.menu-item-indicator |
One frame as the indicator turns on; while it turns off (valued with the state it leaves) until its transitions end. |
Accessibility
- The bar is role=menubar with aria-orientation=horizontal; each trigger is a role=menuitem button with aria-haspopup=menu, aria-expanded and aria-controls, and the menu is labelled by it.
- One tab stop for the bar (roving tabindex). Disabled triggers stay focusable with aria-disabled, as APG asks.
- Menus are not modal: the page stays usable, a press outside closes the open menu and lands where it was aimed.
- The content is role=menu, named by its trigger (aria-labelledby) unless label is set. Items are menuitem, menuitemcheckbox (aria-checked true, false or mixed) and menuitemradio; disabled rows carry aria-disabled.
- Rows take real focus (tabindex=-1, roving) and the focused row carries data-highlighted, so screen readers follow the keyboard.
- A sub trigger has aria-haspopup=menu, aria-expanded and aria-controls; its submenu sits right after it in the DOM, as APG asks.
- A label row inside a group names the group (aria-labelledby).
- Disabled rows stay focusable by keyboard (APG menus), so a screen reader user hears them; the pointer and typeahead skip them.
- The content is popover=manual in the top layer: never clipped by an overflow or transform ancestor, no portal, no z-index.
See also