bwc-menubar
A horizontal bar of top-level menus, built on bwc-menu (src/menu). The
bar itself is a coordinator with no open state of its own: each child
bwc-menu owns its open state through its public API (show() / close()
/ toggle(), the open property, and open-change events), and the
menubar adds roving focus, hover/focus switching, and a single-open
invariant on top.
import "basic-web-components/menu";
import "basic-web-components/menubar";Both imports are needed: menubar does not register bwc-menu itself.
Usage
<bwc-menubar>
<bwc-menu id="file">
<button slot="trigger">File</button>
<div slot="popup">
<button data-menu-item>New</button>
<button data-menu-item>Open</button>
</div>
</bwc-menu>
<bwc-menu id="edit">
<button slot="trigger">Edit</button>
<div slot="popup">
<button data-menu-item>Undo</button>
</div>
</bwc-menu>
</bwc-menubar>
<script>
const bar = document.querySelector("bwc-menubar");
bar.openMenu("edit"); // by child id, or by index: bar.openMenu(1)
bar.closeAll();
</script>Clicking (or Enter on) a trigger toggles its menu through bwc-menu
itself; whenever any menu opens, the bar closes the others. Once a menu is
open, hovering or focusing another trigger switches to it.
API
| Property | Attribute | Type | Default | Notes |
|---|---|---|---|---|
disabled |
disabled |
boolean |
false |
Blocks switching and openMenu; closeAll still works. |
loopFocus |
loop-focus |
boolean |
true |
Wrap focus past the first/last trigger. |
orientation |
orientation |
"horizontal" | "vertical" |
"horizontal" |
Roving direction. Invalid values throw. |
delay |
delay |
number |
100 |
Hover-switch delay in ms once a menu is open. |
closeDelay |
close-delay |
number |
0 |
Leave-the-bar close delay in ms; 0 disables leave-close. |
There is deliberately no open property and no show() / close() /
toggle(): the bar owns no open state. Each child bwc-menu is
individually toggleable, and the bar coordinates through openMenu /
closeAll. There is no trigger-class / onOpenChange either: open
notifications come from the child menus' own open-change events, which
bubble through the bar.
Methods
| Method | Notes |
|---|---|
openMenu(index | id, opts?) |
Open one menu, closing the rest. { focus: true } focuses its trigger. |
closeAll() |
Close every child menu. |
openMenu throws RangeError for an unknown index/id and TypeError
for any other argument type. It is a no-op while disabled or when the
target menu is itself disabled.
Events
The bar emits none of its own. Child bwc-menu open-change events
({ open: boolean }) bubble through it, so one listener on the bar
observes every menu.
Children
Direct bwc-menu children only (nested submenus inside popups are
ignored). Zero child menus throws TypeError. The host gets
role="menubar", aria-orientation, and data-testid="bwc-menubar";
state mirrors as data-open / data-closed (any menu open) and
data-disabled. Triggers keep a toolbar-style roving tabindex
(first enabled trigger is tabbable).
- Keyboard on a trigger:
ArrowLeft/ArrowRight(orUp/Downwhen vertical) roves,Home/Endjumps;Enter/ArrowDownopens through the child menu itself. Printable characters run trigger-label typeahead (500ms window, repeats cycle). While a menu is open, roving (or focusing another trigger) switches to that menu. - Keyboard inside an open popup:
ArrowLeft/ArrowRight(orUp/Downwhen vertical) andHome/Endswitch to the neighboring menu and focus its trigger.Escapeis the child menu's own (closes and refocuses its trigger). - Hover: with every menu closed, hovering does nothing (click to open).
With a menu open, hovering another trigger switches after
delay. Leaving the bar entirely closes everything afterclose-delay(disabled by default).
Form behavior
None.
Errors
TypeErrorfor zerobwc-menuchildren, invalidorientation, or non-finite delays.RangeErrorfromopenMenufor an unknown index/id.