<bmx-tabs>
One panel showing at a time, with a strip of tabs to choose it. Write the panels and nothing else:
7 properties · 1 events · 2 methods · 7 parts
Example
The panel's content is written where the panel is, and the tab is drawn from this element's
label. There is no second element to keep in step and no pair of ids to wire up.
A badge is a short count or status, and it belongs to the panel rather than to the tab.
Give a panel a value when the label is likely to change — a translated label would otherwise change
what the tabs report.
Not reachable while it is disabled.
Vertical, in the pill appearance, and choosing only on Enter:
Show markup
<div class="row">
<bmx-tabs id="ex-tabs" label="Project" style="max-inline-size: 40rem">
<bmx-tab-panel label="Overview">
<p style="margin: 0">
The panel's content is written where the panel is, and the tab is drawn from this element's
<code>label</code>. There is no second element to keep in step and no pair of ids to wire up.
</p>
</bmx-tab-panel>
<bmx-tab-panel label="Members" badge="12">
<p style="margin: 0 0 0.75rem">A badge is a short count or status, and it belongs to the panel rather than to the tab.</p>
<bmx-input label="Find a member" placeholder="Name or email"></bmx-input>
</bmx-tab-panel>
<bmx-tab-panel label="Billing" value="billing">
<p style="margin: 0">
Give a panel a <code>value</code> when the label is likely to change — a translated label would otherwise change
what the tabs report.
</p>
</bmx-tab-panel>
<bmx-tab-panel label="Audit log" disabled>
<p style="margin: 0">Not reachable while it is disabled.</p>
</bmx-tab-panel>
</bmx-tabs>
</div>
<div class="row">
<span class="note" id="ex-tabs-out">Arrow keys move between tabs and choose as they go. Tab moves into the panel.</span>
</div>
<div class="row">
<p style="margin: 0">Vertical, in the pill appearance, and choosing only on Enter:</p>
</div>
<div class="row">
<bmx-tabs orientation="vertical" appearance="pill" activation="manual" label="Settings" style="max-inline-size: 40rem">
<bmx-tab-panel label="General">General settings.</bmx-tab-panel>
<bmx-tab-panel label="Notifications">Notification settings.</bmx-tab-panel>
<bmx-tab-panel label="Security">Security settings.</bmx-tab-panel>
</bmx-tabs>
</div>
<script type="module">
await customElements.whenDefined('bmx-tabs');
const tabs = document.getElementById('ex-tabs');
const out = document.getElementById('ex-tabs-out');
// The event carries the value, the position and the panel element itself -
// the last of those for anything that needs to reach into the panel, such as
// fetching its content the first time it is shown.
tabs.addEventListener('bmxChange', event => {
out.textContent = `bmxChange: value="${event.detail.value}", index ${event.detail.index}`;
});
</script>
<bmx-tabs value="members">
<bmx-tab-panel label="Overview">…</bmx-tab-panel>
<bmx-tab-panel label="Members" badge="12">…</bmx-tab-panel>
</bmx-tabs>
WHERE THE IDS LIVE, AND WHY IT DECIDED THE SHAPE OF THE API
The tabs pattern is held together by two IDREFs - a tab points at its panel
with aria-controls, the panel points back with aria-labelledby - and an
IDREF does not cross a shadow boundary. That single fact ruled out the
three-element design most libraries use, where the author writes the tabs and
the panels as siblings: the strip is drawn here, inside this component's
shadow root, so a tab in the author's tree could never see the panel and the
reference would resolve to nothing. Not throw - resolve to nothing, which
looks perfect on screen and fails an audit.
So both ends stay in this root: the tabs are drawn here, and so is the
region that holds the panels. The author's bmx-tab-panel elements are
slotted into that region, which puts them inside it in the flattened tree -
the same reasoning that put role="radiogroup" on a <div> in
bmx-radio-group rather than on its host.
One region rather than one per panel, and that is deliberate too: only
one panel is ever showing, so one role="tabpanel" labelled by whichever tab
is selected describes exactly what is on the screen. Every tab's
aria-controls names that region.
ACTIVATION
auto (the default) selects as the arrows move, which is what WAI-ARIA
recommends and what a native tab strip does. manual moves focus and waits
for Enter or Space - the right choice when a panel fetches its content, where
arrowing across four tabs to reach the fifth would fire four requests.
The tab stop follows the selection rather than the focus, so tabbing away and back returns to the panel that is showing rather than to wherever the arrows were last left.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
activation |
activation |
BmxPanelActivation |
'auto' |
Whether the arrows choose as they move, or only move. |
appearance |
appearance |
BmxTabsAppearance |
'underline' |
How the strip is drawn. |
label |
label |
string |
'Tabs' |
The tab list's accessible name. |
orientation |
orientation |
BmxOrientation |
'horizontal' |
Which way the strip runs. Vertical moves on Up and Down instead. |
size |
size |
BmxSize |
'md' |
Size step, applied to the tabs. |
stretch |
stretch |
boolean |
false |
Whether the tabs share the strip's width equally. |
value |
value |
string |
— | The panel showing, by value. Mutable, so pressing a tab updates it. A consumer driving it may treat it as controlled and watch bmxChange. A value naming a panel that does not exist - or one that has been removed, or disabled - falls back to the first panel that can be shown rather than leaving the component blank. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxTabsChangeDetail |
Fired when a different panel is chosen. |
Methods
| Method | Signature | Description |
|---|---|---|
setFocus |
setFocus(options?: FocusOptions) => Promise<void> |
Focus the selected tab. |
showPanel |
showPanel(value: string) => Promise<void> |
Show a panel by value. Ignored if nothing has that value, or it is disabled. |
Slots
| Slot | Description |
|---|---|
(default) |
bmx-tab-panel elements. |
CSS shadow parts
| Part | Description |
|---|---|
panels |
The region the panels are shown in. |
tab |
One tab. The selected one also matches tab-selected. |
tab-badge |
The badge within a tab. |
tab-icon |
The icon within a tab. |
tab-label |
The label within a tab. |
tab-selected |
The selected tab. |
tablist |
The strip of tabs. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-tab-color |
A tab's text colour when it is not selected. |
--bmx-tab-indicator-size |
Thickness of the line marking the selected tab. |
--bmx-tab-padding-block |
Space inside a tab, down. |
--bmx-tab-padding-inline |
Space inside a tab, across. |
--bmx-tab-radius |
Corner radius of a tab, in the pill and enclosed appearances. |
--bmx-tab-selected-color |
A tab's text colour when it is. |
--bmx-tabs-gap |
Space between one tab and the next. |
--bmx-tabs-panel-padding |
Space between the strip and the panel below it. |