<bmx-split-button>
A default action joined to a menu of alternatives - "Save" with "Save as…" and "Save a copy" behind the chevron. The pattern earns its keep wherever there is one obvious action and three rarer ones: it keeps the obvious one a single click away instead of demoting it into a dropdown with its siblings.
11 properties · 3 events · 2 methods · 6 parts
Example
Show markup
<div class="row">
<bmx-split-button id="docs-split-save" label="Save">Save</bmx-split-button>
<bmx-split-button id="docs-split-export" variant="outline" tone="neutral" label="Export" placement="bottom-end">Export</bmx-split-button>
</div>
<script>
// `items` is an array, so it is set as a property. An HTML attribute can only
// ever be a string; an array pushed through one arrives as "[object Object]".
//
// Wait for the element to be defined first. This script runs while the page
// parses, which is before the deferred runtime has registered anything, and a
// property set on an element that has not upgraded yet may not survive the
// upgrade. `whenDefined` is the reliable order, and it is what you want in
// your own application too.
customElements.whenDefined('bmx-split-button').then(() => {
document.getElementById('docs-split-save').items = [
{ id: 'save-as', label: 'Save as…', shortcut: '⇧⌘S' },
{ id: 'save-copy', label: 'Save a copy', description: 'Keeps the original untouched' },
{ separator: true },
{ id: 'discard', label: 'Discard changes', danger: true },
];
document.getElementById('docs-split-export').items = [
{ id: 'xlsx', label: 'Excel workbook' },
{ id: 'csv', label: 'CSV', shortcut: '⌘E' },
{ id: 'pdf', label: 'PDF', disabled: true },
];
});
</script>
WHAT IS LEFT HERE
The two halves, and the debt-free part of the relationship between them. The
menu is bmx-menu, which owns the surface, the placement, the roving
tabindex, the typeahead and the focus. This component owns its trigger, and
translates between the menu's vocabulary and its own.
That translation is not ceremony. Every bmx- event is composed, so a
bmxSelect emitted by the menu inside this shadow root would reach a
consumer of the split button alongside the one this component emits - and
the consumer would see every choice twice. Each inner event is therefore
stopped at the boundary and re-published deliberately, which is the rule in
§8: an event that escapes a shadow root is part of that component's API
whether it was meant to be or not.
HOW THE DELEGATION WAS MADE SAFE
The menu used to be built into this file. It was taken out under fifty-three
characterisation tests written against the behaviour as it stood - including
five that do nothing but count events - and not one of them was edited to
accommodate the move. The single exception is recorded in the test file
itself: the menu now answers a typed letter, because bmx-menu has the
typeahead the WAI-ARIA menu pattern asks for and this component never did.
SUBMENUS
An entry with its own items opens a submenu, to any depth, exactly as it
does in bmx-menu - "Export" with three formats under it is the case that
earns it. That arrived with the delegation rather than being designed here,
which made it worth a deliberate decision: either test and document it or
refuse it, because an undocumented accident is the one option that is wrong.
It is documented, and the choice reports a path so a nested item is
identifiable. Keep them shallow. A split button is a short list of
alternatives to one obvious action, and a menu deep enough to need navigating
is a sign the obvious action is not obvious.
WHY THE MENU IS ANCHORED TO THE WHOLE COMPONENT
Aligning to the chevron alone would hang a wide menu off a narrow button;
aligning to both halves is what makes bottom-start line up with the primary
action's leading edge, which is the edge the eye is already on. Focus, by
contrast, belongs on the chevron the user actually pressed - which is why the
menu is told those two things separately.
ACCESSIBILITY
The trigger carries aria-haspopup="menu" and aria-expanded and points at
the surface the menu renders; everything inside the menu - the roles, the
roving tabindex, focus moving in on open and back to the trigger on close -
is bmx-menu's, and is the same in every component that uses it.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
action |
property only | (event: unknown) => unknown | Promise<unknown> |
— | An async handler for the primary action, driving its busy state. |
disabled |
disabled |
boolean |
false |
Disable both halves. |
items |
property only | BmxMenuEntry[] |
[] |
The menu's entries. A property, because it is an array. Accepts the JSON spelling of the list as well, because an attribute is the only channel some templates have. See src/core/markup.ts. |
label |
label |
string |
— | The primary action's accessible name, when it has no visible label. |
loading |
loading |
boolean |
false |
Show the primary action as busy. |
menuLabel |
menu-label |
string |
'More actions' |
The trigger's accessible name. |
placement |
placement |
'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' |
'bottom-start' |
Which side the menu opens on when there is room. |
shape |
shape |
BmxShape |
'rounded' |
Corner treatment of the outer edges. |
size |
size |
BmxSize |
'md' |
Size step. |
tone |
tone |
BmxTone |
'primary' |
Semantic colour role. |
variant |
variant |
BmxButtonVariant |
'solid' |
Visual treatment, applied to both halves. |
Events
| Event | Detail | Description |
|---|---|---|
bmxActivate |
void |
Fired when the primary action is activated. |
bmxOpenChange |
boolean |
Fired when the menu opens or closes. |
bmxSelect |
BmxSplitButtonSelectDetail |
Fired when a menu item is chosen. |
Methods
| Method | Signature | Description |
|---|---|---|
closeMenuNow |
closeMenuNow() => Promise<void> |
Close the menu. Focus returns to the trigger. |
openMenu |
openMenu() => Promise<void> |
Open the menu and move focus into it. |
Slots
| Slot | Description |
|---|---|
(default) |
The primary action's label. |
start |
An icon for the primary action. |
CSS shadow parts
| Part | Description |
|---|---|
action |
The primary action button. |
base |
The container. |
item |
A menu item. |
menu |
The menu surface. |
separator |
A divider between items. |
trigger |
The menu trigger button. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-button-radius |
Corner radius of the outer edges. The shared edge between the two halves is always square. |
--bmx-effect-duration |
Passed through to both halves' click effects. |