Menu Bar
import { MenuBar } from '@absoluteui/components/menu-bar';import '@absoluteui/components/menu-bar/style.css';Using Angular? See Menu Bar for Angular.
What it does
Section titled “What it does”A horizontal application menubar following the WAI-ARIA APG Menubar pattern. The bar is a role="menubar" row of role="menuitem" buttons with a single tab stop (roving tabindex). Items with items open a Menu, which is the same component and engine as Menu, so submenus, checkbox and radio items, typeahead, and the overlay stack behave identically. Items without items are commands: they open nothing and emit aui-select. While a menu is open, ArrowLeft/ArrowRight (mirrored in RTL) and pointer hover move to the neighbouring menu.
When to use
Section titled “When to use”- A persistent application command bar in the style of desktop software (File, Edit, View, Help) in editors and tools.
- Several groups of commands that each need a menu, where a single Menu button would be too deep.
When not to use
Section titled “When not to use”- Site or app navigation made of links: use navigation links or Tabs, not role=menubar.
- A single overflow or row-actions menu: use Menu.
- A row of always-visible buttons without menus: use a toolbar or Button Group.
- Vertical navigation (side menus): the menu bar is horizontal only.
Examples
Section titled “Examples”Editor menubar with menus, a submenu, and a command item
Section titled “Editor menubar with menus, a submenu, and a command item”import { MenuBar, type MenuBarSelectDetail } from '@absoluteui/components/menu-bar';
const host = document.createElement('div');document.body.append(host);MenuBar.create(host, { label: 'Editor', items: [ { id: 'file', label: 'File', items: [ { id: 'new', label: 'New', shortcut: 'Ctrl+N' }, { id: 'open-recent', label: 'Open recent', items: [{ id: 'recent-report', label: 'report.md' }] }, { type: 'separator' }, { id: 'close-file', label: 'Close', tone: 'danger' }, ] }, { id: 'edit', label: 'Edit', items: [ { id: 'undo', label: 'Undo', shortcut: 'Ctrl+Z' }, { id: 'redo', label: 'Redo', shortcut: 'Ctrl+Y' }, ] }, { id: 'help', label: 'Help' }, ],});// Command items and menu items both bubble aui-select to the host.host.addEventListener('aui-select', (event) => { const { id } = (event as CustomEvent<MenuBarSelectDetail>).detail; host.dataset.lastCommand = id;});Checkbox and radio items that keep the menu open
Section titled “Checkbox and radio items that keep the menu open”import { MenuBar, type MenuBarSelectDetail } from '@absoluteui/components/menu-bar';
const host = document.querySelector<HTMLElement>('#app-menu');if (host) { const bar = MenuBar.create(host, { label: 'Application', closeOnSelect: false, items: [ { id: 'view', label: 'View', items: [ { type: 'checkbox', id: 'grid', label: 'Show grid', checked: true }, { type: 'group', label: 'Zoom', items: [ { type: 'radio', id: 'zoom-100', label: '100%', group: 'zoom', checked: true }, { type: 'radio', id: 'zoom-200', label: '200%', group: 'zoom', checked: false }, ] }, ] }, { id: 'tools', label: 'Tools', disabled: true, items: [{ id: 'macros', label: 'Macros' }] }, ], }); host.addEventListener('aui-select', (event) => { const { id, checked } = (event as CustomEvent<MenuBarSelectDetail>).detail; if (checked !== undefined) host.dataset[id] = String(checked); }); bar.menu('view')?.element.addEventListener('aui-close', () => { host.dataset.viewMenu = 'closed'; });}Global defaults
Section titled “Global defaults”import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { menuBar: { closeOnSelect: false, typeahead: false } } });import { MenuBar, type MenuBarSelectDetail } from '@absoluteui/components/menu-bar';import '@absoluteui/components/menu/style.css';import '@absoluteui/components/menu-bar/style.css';
const host = document.querySelector<HTMLElement>('#app-menu');if (host) { const bar = MenuBar.create(host, { label: 'Editor', items: [ { id: 'file', label: 'File', items: [ { id: 'new', label: 'New', shortcut: 'Ctrl+N' }, { id: 'open-recent', label: 'Open recent', items: [{ id: 'recent-report', label: 'report.md' }] }, ] }, { id: 'view', label: 'View', items: [{ type: 'checkbox', id: 'grid', label: 'Show grid', checked: true }] }, { id: 'help', label: 'Help' }, ], }); host.addEventListener('aui-select', (event) => { const { id, checked } = (event as CustomEvent<MenuBarSelectDetail>).detail; if (id === 'help') bar.close(); if (checked !== undefined) host.dataset[id] = String(checked); });}More usage
Section titled “More usage”import { MenuBar } from '@absoluteui/components/menu-bar';
const host = document.querySelector<HTMLElement>('#app-menu');if (host) { MenuBar.create(host, { label: 'Editor', items: [ { id: 'file', label: 'File', items: [{ id: 'new', label: 'New' }, { id: 'save', label: 'Save' }] }, { id: 'edit', label: 'Edit', items: [{ id: 'undo', label: 'Undo' }] }, { id: 'help', label: 'Help' }, ], });}Anatomy
Section titled “Anatomy”<div class="aui-menu-bar"> (consumer host; children replaced) <div class="aui-menu-bar__bar" role="menubar" aria-label="Editor"> <div class="aui-menu-bar__entry" role="none"> (display: contents) <button class="aui-menu-bar__item" role="menuitem" tabindex="0" data-id="file" aria-haspopup="menu" aria-expanded="false" aria-controls=menu> <svg class="aui-menu-bar__icon"> (optional) <span class="aui-menu-bar__label">File</span> </button> <div class="aui-menu" role="menu" popover="manual" aria-labelledby=button> (Menu popup, top layer) <div class="aui-menu aui-menu--submenu" role="menu"> (one per submenu) </div> <div class="aui-menu-bar__entry" role="none"> <button class="aui-menu-bar__item" role="menuitem" tabindex="-1" data-id="help">Help</button> (command) </div> </div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”MenuBar.create(host: HTMLElement, options: MenuBarOptions): MenuBarGlobal configuration key: menuBar (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Accessible name of the menubar (aria-label). Must be a non-empty string. Required. | |
items |
readonly MenuBarItem[] |
Non-empty list of menubar items { id, label, icon?, disabled?, items? }. Items with items open a Menu; items without are commands. Ids are unique across the bar and all of its menus. Required. | |
renderItem |
(item: MenuSelectableItem, context: MenuRenderContext) => Node |
Custom item content (since 0.9.0); the label stays the accessible name and typeahead text. Nodes are reused per item and released with releaseNode when dropped. | |
icons |
Partial<{ submenu: IconRef; checked: IconRef; radio: IconRef }> |
Icon slot overrides passed to every menu. | |
closeOnSelect |
boolean |
true |
Menus close after activating a checkbox or radio item. Action items always close the menu. Globally configurable. |
typeahead |
boolean |
true |
Printable characters move focus to the next bar item (skipping disabled items) or menu item whose label starts with them. Globally configurable. |
direction |
'ltr' | 'rtl' |
"ltr" |
Arrow-key mirroring and menu/submenu sides. An inherited dir=rtl on the host also makes the bar RTL. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
The host element (class aui-menu-bar); its children are replaced. |
element |
HTMLDivElement |
The role=menubar element inside the host. |
options |
Readonly<MenuBarOptions> |
Current options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
menu(id: string): Menu | null |
The Menu opened by a menubar item, or null for commands, disabled items, and unknown ids. |
close(): void |
Close any open menu; focus returns to its menubar item when it was inside the menu. |
update(options: Partial<MenuBarOptions>): void |
Merge, validate, and re-render. Focus and the tab stop stay on the same item id; open menus close and menu state resets. A failed update keeps the previous options. |
destroy(): void |
Destroy every menu, remove listeners and subscriptions, empty the host, and remove the aui-menu-bar class. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-select |
{ id: string; item: MenuBarItem | MenuSelectableItem; checked?: boolean } |
Yes | No | A command bar item or an enabled menu item was activated. Always dispatched on the host (menu selections are re-dispatched there). checked is the new state for checkbox and radio items. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-menu-bar |
Host element. |
aui-menu-bar--rtl |
Set on the host when the effective direction is RTL. |
aui-menu-bar__bar |
The role=menubar row (wrapping flex). |
aui-menu-bar__entry |
role=none wrapper (display: contents) around each item and its menu popup. |
aui-menu-bar__item |
Bar item button (role=menuitem); [aria-expanded=‘true’] while its menu is open. |
aui-menu-bar__icon | __label |
Item parts. |
aui-menu (and all aui-menu__* classes) |
Menu popups opened from the bar; styled by @absoluteui/components/menu/style.css. |
Icon slots
Section titled “Icon slots”| Slot | Default icon |
|---|---|
submenu |
chevron-right |
checked |
check |
radio |
check |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-focus-ring | --aui-color-hover | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-control-height-sm | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-space-1 | --aui-space-2 | --aui-space-3
TypeScript definitions
Section titled “TypeScript definitions”interface MenuBarItem { id: string; label: string; icon?: IconRef; disabled?: boolean; items?: readonly MenuItem[] }interface MenuBarConfig { closeOnSelect: boolean; typeahead: boolean }interface MenuBarOptions extends Partial<MenuBarConfig> { label: string; items: readonly MenuBarItem[]; direction?: 'ltr' | 'rtl'; icons?: Partial<MenuIcons>;}type MenuBarSelectDetail = MenuSelectDetail; // { id: string; item: MenuSelectableItem; checked?: boolean }MenuItem, MenuSelectableItem, MenuSelectDetail, and MenuIcons are re-exported from @absoluteui/components/menu-bar. For a command bar item, detail.item is the MenuBarItem object.
Accessibility
Section titled “Accessibility”Pattern: WAI-ARIA APG Menubar with Menu popups (the Menu component and engine); roving tabindex on the bar, real DOM focus on menu items.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Page | Moves focus to or from the bar; the bar is one tab stop (the last focused item, else the first enabled item). |
| ArrowRight / ArrowLeft (mirrored in RTL) | Bar item focused | Next / previous bar item, wrapping. Disabled items receive focus. |
| Home / End | Bar item focused | First / last bar item. |
| Printable characters | Bar item focused, typeahead=true | Focus the next enabled bar item whose label starts with the typed characters. |
| ArrowDown / Enter / Space | Bar item with a menu | Opens its menu and focuses the first enabled item. |
| ArrowUp | Bar item with a menu | Opens its menu and focuses the last enabled item. |
| Enter / Space | Command bar item (no items) | Emits aui-select; focus stays on the item. |
| ArrowDown / ArrowUp / Home / End / typeahead | Menu open | Menu navigation (same as Menu). |
| ArrowRight (ArrowLeft in RTL) | Menu open, submenu parent focused | Opens the submenu (same as Menu). |
| ArrowLeft (ArrowRight in RTL) | Submenu open | Closes the submenu and returns to its parent item (same as Menu). |
| ArrowRight / ArrowLeft (mirrored in RTL) | Menu open, key not handled by the menu | Closes the menu, moves to the neighbouring bar item (wrapping), and opens its menu with focus on its first item; command items only receive focus. |
| Enter / Space | Menu item focused | Activates it (same as Menu); action items close the menu and return focus to the bar item. |
| Escape | Top-level menu open | Closes the menu and returns focus to its bar item. Submenus close one level at a time. |
| Tab / Shift+Tab | Menu open | Closes all levels, returns focus to the bar item, and moves on out of the bar. |
| Element | Role |
|---|---|
| bar | menubar |
| item wrappers | none |
| bar items | menuitem (native <button>) |
| menu popups and submenus | menu |
| menu items | menuitem, menuitemcheckbox, or menuitemradio |
| icons | none (aria-hidden=true) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-label |
bar | The label option |
aria-haspopup |
bar items with items | “menu” (also on disabled items with items) |
aria-expanded |
bar items with a menu | “true” while the menu is open, otherwise “false” |
aria-controls |
bar items with a menu | id of the menu popup |
aria-labelledby |
menu popup | id of its bar item |
aria-disabled |
disabled bar items | “true”; the item stays focusable but never opens or emits |
tabindex |
bar items | 0 on the current item (roving tab stop), -1 on the others |
The bar has one tab stop (roving tabindex follows focus). Opening a menu moves real DOM focus to its first (or last) enabled item; activation, Escape at the top level, Tab, and close() with focus inside return focus to the bar item. Bar items show color-hover while hovered or expanded and a color-focus-ring outline on :focus-visible.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 1.4.3 Contrast (Minimum) | AA | |
| 1.4.11 Non-text Contrast | AA | |
| 2.1.1 Keyboard | A | |
| 2.1.2 No Keyboard Trap | A | |
| 2.4.3 Focus Order | A | |
| 2.4.7 Focus Visible | AA | |
| 4.1.2 Name, Role, Value | A |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Disabled items stay focusable, per the APG, so screen-reader users discover them.
- Hovering another bar item while a menu is open switches menus (pointer movement only; touch never switches).
- Each menu level is a separate overlay-stack entry, so Escape and outside presses close the topmost level first, including inside a Dialog.
- aui-select ids never collide: ids are validated as unique across the bar and all menus.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
- A global configuration change or update() while a menu is open closes it without restoring focus to the bar item.
Behavior
Section titled “Behavior”States
Section titled “States”idle, bar item focused (:focus-visible adds the ring), menu open (bar item aria-expanded="true" keeps the hover background), submenu open, bar item disabled.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| idle | Tab into the bar | the tab-stop item focused |
| item focused | ArrowLeft/ArrowRight, Home/End, typeahead | another bar item focused; tab stop moves with it |
| item with a menu focused | click, Enter, Space, ArrowDown / ArrowUp | menu open; first / last enabled item focused; aui-open on the menu |
| command item focused | click, Enter, Space | aui-select; focus stays |
| menu open | unhandled ArrowLeft/ArrowRight | menu closed; neighbouring bar item focused and its menu opened (commands are only focused) |
| menu open | pointer moves over another bar item | same as above |
| menu open | activate an action item | menu closed; focus on the bar item; aui-select |
| menu open | Escape at the top level | menu closed; focus on the bar item |
| menu open | Tab, outside press, focus moving elsewhere, close() |
menu closed |
| any | update({ items }) or update({ icons }) |
rebuilt; open menus closed |
| any | other update(), global menuBar configuration change |
applied in place; an open menu stays open |
Accessibility notes
Section titled “Accessibility notes”- One tab stop keeps the bar cheap to pass for keyboard users; the tab stop remembers the last focused item.
- Disabled items stay focusable (APG) so users can discover them.
- Hover switching follows real pointer movement only; touch never switches menus.
- Focus returns to the bar item on activation, Escape, and Tab from a menu.
update()or a global configuration change while a menu is open closes it without restoring focus (see Known limitations).- Manual screen-reader verification is pending (see Known limitations).
Responsive behavior
Section titled “Responsive behavior”The bar is a wrapping flex row: on narrow widths items wrap onto further lines and labels wrap anywhere. There is no overflow (“more”) collapse. Menus size and position like Menu popups and flip and shift to stay in the viewport.
Right-to-left
Section titled “Right-to-left”The effective direction is RTL when direction (or the global direction) is 'rtl' or the host inherits dir="rtl". Then ArrowLeft moves to the next bar item, menu switching is mirrored, menus open bottom-start aligned to the item’s right edge, submenus open to the left, and the host gets aui-menu-bar--rtl. Layout uses logical properties.
Theming
Section titled “Theming”Colors come from role tokens only. In forced-colors mode the bar has a CanvasText border, the expanded item is drawn in Highlight / HighlightText, and disabled items in GrayText. Menus follow the Menu theme.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { menuBar: { closeOnSelect: false, typeahead: false } } });A change applies to existing bars in place (open menus stay open). The global menu configuration does not apply to menubar menus: the bar passes placement: 'bottom-start', closeOnSelect, typeahead, and direction explicitly.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic through update(); only items and icons rebuild the bar.
icons.submenu (default chevron-right), icons.checked (default check), and icons.radio (default check), passed to every menu. Bar items and menu items take a per-item icon.
Security
Section titled “Security”All text uses textContent; nothing is parsed as HTML. Positioning writes styles through the CSSOM. CSP-safe.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry. Menu checkbox and radio state is kept in memory and reset when the bar is rebuilt (update({ items }) or update({ icons })).
Performance
Section titled “Performance”All menus are built on create and when items or icons change. Four delegated listeners on the bar, the listeners of each Menu, and one configuration subscription. Budget: under 50 ms for MenuBar.create with 8 menus of 20 items.
Supported data scale
Section titled “Supported data scale”Tested with 8 bar items of 20 menu items each and submenus two levels deep. Keep the bar short enough to fit on one line at common widths.
Edge cases
Section titled “Edge cases”- Ids must be unique across the bar and all menus; a bar item and a menu item with the same id throw
MENU_BAR_OPTIONS. - A bar item with
items: []throws; omititemsfor a command. - A disabled item with
itemsgets no Menu:menu(id)returnsnull, and it cannot be opened. - ArrowRight on a leaf item inside a submenu moves to the next bar item (the menu does not handle it).
- Moving to a command item while a menu is open closes the menu and focuses the command; nothing opens.
update({ items })keeps focus and the tab stop on the same id when it still exists; otherwise the tab stop moves to the first enabled item.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
MENU_BAR_OPTIONS |
configuration | Empty or non-string label; items not a non-empty array; an item that is not an object; missing or empty id or label; duplicate id across the bar and menus; empty items on a bar item or submenu; invalid menu items (as in Menu). |
CFG_INVALID_VALUE |
configuration | Invalid closeOnSelect, typeahead, or direction. |
A failed construction leaves the host untouched: no rendering, class, listeners, or subscriptions. A failed update() keeps the previous options.
Known limitations
Section titled “Known limitations”- Horizontal only; no overflow collapse (items wrap).
update({ items })andupdate({ icons })rebuild every menu: an open menu closes, focus inside it is not restored, and checkbox/radio state resets.- An explicit
direction: 'ltr'does not override an inheriteddir="rtl". Menus read the direction at render time, so a laterdirchange reaches them only on the next render. - Menu placement is fixed at
bottom-start. - Submenus inherit the Menu limitations (150 ms hover delay without a pointer-safe triangle; shortcut text is a hint only).
- Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
Unsupported scenarios
Section titled “Unsupported scenarios”- Vertical menubars.
- Link items (
href); navigate in theaui-selectlistener. - HTML or custom nodes inside items.
- Checkbox or radio items directly on the bar.