<bmx-toolbar>
A bar of controls that does not break when the window does. When there is no longer room for everything, the items that do not fit move into a menu behind one button - and they keep working from there, because choosing one activates the element you wrote rather than a copy of it.
5 properties · 1 events · 2 methods · 3 parts
Example
Drag the handle to narrow the bar, and watch what does not fit move into the menu:
data-bmx-fixed, so it never collapses however narrow the bar gets.
Show markup
<div class="row">
<p style="margin: 0">Drag the handle to narrow the bar, and watch what does not fit move into the menu:</p>
</div>
<div class="row">
<div id="ex-toolbar-frame" style="inline-size: 100%; resize: horizontal; overflow: auto; min-inline-size: 12rem; padding-block-end: 8px">
<bmx-toolbar id="ex-toolbar" label="Formatting">
<bmx-button variant="ghost" tone="neutral">Bold</bmx-button>
<bmx-button variant="ghost" tone="neutral">Italic</bmx-button>
<bmx-button variant="ghost" tone="neutral">Underline</bmx-button>
<hr />
<bmx-button variant="ghost" tone="neutral">Bullet list</bmx-button>
<bmx-button variant="ghost" tone="neutral">Numbered list</bmx-button>
<hr />
<bmx-button variant="ghost" tone="neutral">Insert link</bmx-button>
<bmx-button variant="ghost" tone="neutral">Insert image</bmx-button>
<bmx-button data-bmx-fixed>Publish</bmx-button>
</bmx-toolbar>
</div>
</div>
<div class="row">
<span class="note" id="ex-toolbar-out">
<strong>Publish</strong> carries <code>data-bmx-fixed</code>, so it never collapses however narrow the bar gets.
</span>
</div>
<script type="module">
await customElements.whenDefined('bmx-toolbar');
const out = document.getElementById('ex-toolbar-out');
/*
* Every button has one handler, and it does not care whether the press came
* from the bar or from the overflow menu: choosing an item in the menu calls
* the original element's own `activate()`, so this listener hears both.
*/
for (const button of document.querySelectorAll('#ex-toolbar bmx-button')) {
button.addEventListener('bmxActivate', () => {
out.textContent = `pressed: ${button.textContent.trim()}`;
});
}
</script>
<bmx-toolbar label="Formatting">
<bmx-button variant="ghost">Bold</bmx-button>
<bmx-button variant="ghost">Italic</bmx-button>
<hr />
<bmx-button variant="ghost">Insert link</bmx-button>
<bmx-input data-bmx-fixed placeholder="Search"></bmx-input>
</bmx-toolbar>
WHY NOT bmx-button-group
They look alike and answer different questions. A button group is a set of
related buttons drawn as one control - a segmented view switcher, a run of
toggles - and it is selection that makes it interesting. A toolbar is a
bar: mixed controls, dividers between them, and one thing a group has no
opinion about, which is what happens when the bar is narrower than its
contents. Put groups inside a toolbar; they collapse as single items.
WHAT COLLAPSES, AND IN WHAT ORDER
From the end, always. Never by size and never by a guess at importance: a bar
that reshuffled itself as the window moved would make "the third button"
worthless as a thing to remember. Mark anything that must survive with
data-bmx-fixed - a search field, a primary action - and its room is taken
off the top. The arithmetic is src/core/toolbar.ts, tested without a
browser.
HOW AN OVERFLOWED ITEM STILL WORKS
The menu is a proxy, not a copy. Choosing an entry calls the original
element's own activate() where it has one - every bmx-button does - and
otherwise clicks it. So the handler you wrote runs, whatever it is, and there
is no second code path to keep in step with the first. The menu's label for
an item comes from data-bmx-label, then the element's label or
aria-label, then its text.
THE KEYBOARD
One Tab stop, arrows within, as WAI-ARIA's toolbar pattern asks. The overflow
trigger is the last stop on the bar. A bmx-button is told its tabindex
through rovingTabindex, because the focusable element inside it is in a
shadow root this component cannot reach; a plain focusable element gets its
tabIndex set directly. A control with its own inner keyboard - a text field
- keeps its own tab stop rather than being folded into the roving set, which is what the pattern asks for and what a user typing into it expects.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
collapse |
collapse |
'auto' | 'never' |
'auto' |
Whether items that do not fit move into a menu. never leaves the bar to overflow however the page's CSS says it should - which is the right answer when the toolbar is inside something that already scrolls. |
label |
label |
string |
'Toolbar' |
The toolbar's accessible name. |
menuLabel |
menu-label |
string |
'More' |
The overflow button's accessible name. |
orientation |
orientation |
BmxOrientation |
'horizontal' |
Which way the bar runs. |
wrapFocus |
wrap-focus |
boolean |
true |
Whether the arrows wrap from one end of the bar to the other. |
Events
| Event | Detail | Description |
|---|---|---|
bmxSelect |
BmxToolbarSelectDetail |
Fired when an item is chosen from the overflow menu. |
Methods
| Method | Signature | Description |
|---|---|---|
refresh |
refresh() => Promise<void> |
Re-measure now. Rarely needed: a resize and a change of children are watched. |
setFocus |
setFocus() => Promise<void> |
Focus the toolbar, landing on whichever item holds its tab stop. |
Slots
| Slot | Description |
|---|---|
(default) |
The toolbar's controls, and <hr> elements as dividers. |
CSS shadow parts
| Part | Description |
|---|---|
base |
The bar. |
menu |
The overflow menu's surface. |
trigger |
The overflow button. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-toolbar-background |
The bar's background. |
--bmx-toolbar-border-color |
The bar's border, and the dividers on it. |
--bmx-toolbar-gap |
Space between one item and the next. |
--bmx-toolbar-padding |
Space inside the bar. |
--bmx-toolbar-radius |
Corner radius of the bar. |
--bmx-toolbar-separator-inset |
How far a divider stops short of the bar's edges. |