Tabs
WAI-ARIA tabs — underline or pill (segmented) style; aria-selected drives the visuals, membrane.js adds click and arrow-key behavior.
Examples
Underline tabs
One <link> to membrane.css on jsDelivr.
Download the three CSS files into your project.
Edit the JSON source and run the build.
HTML for “Underline tabs”
<div class="mp-tabs">
<div class="mp-tabs__list" role="tablist" aria-label="Install options">
<button class="mp-tabs__tab" type="button" role="tab" id="tabs-cdn" aria-controls="tabs-cdn-panel" aria-selected="true">CDN</button>
<button class="mp-tabs__tab" type="button" role="tab" id="tabs-copy" aria-controls="tabs-copy-panel" aria-selected="false" tabindex="-1">Copy files</button>
<button class="mp-tabs__tab" type="button" role="tab" id="tabs-fork" aria-controls="tabs-fork-panel" aria-selected="false" tabindex="-1">Fork</button>
</div>
<div class="mp-tabs__panel" role="tabpanel" id="tabs-cdn-panel" aria-labelledby="tabs-cdn" tabindex="0"><p class="type-small">One <code class="mp-code"><link></code> to <code class="mp-code">membrane.css</code> on jsDelivr.</p></div>
<div class="mp-tabs__panel" role="tabpanel" id="tabs-copy-panel" aria-labelledby="tabs-copy" tabindex="0" hidden><p class="type-small">Download the three CSS files into your project.</p></div>
<div class="mp-tabs__panel" role="tabpanel" id="tabs-fork-panel" aria-labelledby="tabs-fork" tabindex="0" hidden><p class="type-small">Edit the JSON source and run the build.</p></div>
</div>
Pill tabs
Warm cream, terracotta emphasis.
Charcoal depth, lightened accents.
HTML for “Pill tabs”
<div class="mp-tabs" data-variant="pill">
<div class="mp-tabs__list" role="tablist" aria-label="Theme preview">
<button class="mp-tabs__tab" type="button" role="tab" id="tabs-light" aria-controls="tabs-light-panel" aria-selected="true">Light</button>
<button class="mp-tabs__tab" type="button" role="tab" id="tabs-dark" aria-controls="tabs-dark-panel" aria-selected="false" tabindex="-1">Dark</button>
</div>
<div class="mp-tabs__panel" role="tabpanel" id="tabs-light-panel" aria-labelledby="tabs-light" tabindex="0"><p class="type-small">Warm cream, terracotta emphasis.</p></div>
<div class="mp-tabs__panel" role="tabpanel" id="tabs-dark-panel" aria-labelledby="tabs-dark" tabindex="0" hidden><p class="type-small">Charcoal depth, lightened accents.</p></div>
</div>
Guidance
Switch between views of related content without leaving the page. The visual state is driven entirely by aria-selected, so the markup and what screen readers hear can't disagree. Load components/membrane.js for behavior (click, Arrow/Home/End keys, RTL); without it, tabs render but don't switch.
When not to use
- Navigating between pages → links in a Navbar.
- Content users need to compare side by side → show it all.
Accessibility
- Required wiring:
role="tablist"(witharia-label),role="tab"+aria-controls+aria-selected,role="tabpanel"+aria-labelledby; unselected tabs havetabindex="-1"and their panelshidden. - Keyboard (via
membrane.js): Tab enters the list on the selected tab; ←/→ move and select (mirrored in RTL); Home/End jump; Tab again moves into the panel.
Manual screen-reader and usability checks have not been recorded for this inventory. See the accessibility evidence for automated coverage, remaining checks and the result-recording protocol.
API
| Class | Kind |
|---|---|
.mp-tabs | root |
.mp-tabs__list | part of .mp-tabs |
.mp-tabs__tab | part of .mp-tabs |
.mp-tabs__panel | part of .mp-tabs |
| Attribute | Values |
|---|---|
data-variant | pill |
States styled: :focus-visible :hover [aria-selected="true"]. Set the native attribute or ARIA state; there are no state classes.
| Token | Default | Light · dark |
|---|---|---|
--mp-tabs-fg | var(--color-text-secondary) | #5C564E · #A39D93 |
--mp-tabs-fg-hover | var(--color-text-primary) | #2A2622 · #F0EBE1 |
--mp-tabs-fg-selected | var(--color-text-primary) | #2A2622 · #F0EBE1 |
--mp-tabs-bg-hover | var(--color-bg-hover) | rgba(42, 38, 34, 0.06) · rgba(240, 235, 225, 0.06) |
--mp-tabs-indicator | var(--color-action-primary) | #B35530 · #D4724F |
--mp-tabs-track | var(--color-bg-tertiary) | #E2DCD2 · #2E2E34 |
--mp-tabs-bg-selected | var(--color-bg-primary) | #FAF6EE · #1A1B1F |
Design tokens consumed: --border-default --border-width-medium --border-width-thin --color-action-primary --color-bg-hover --color-bg-primary --color-bg-tertiary --color-text-primary --color-text-secondary --elevation-raised --focus-ring --focus-ring-offset --font-sans --radius-full --radius-sm --size-control-md --size-control-sm --size-touch-target --space-1 --space-2 --space-4 --text-small --transition-fast --transition-state --weight-medium
Verified contrast
The build measures each pair for the defaults and for every tone, variant and state that reassigns a token, over page and card surfaces. The ratios below are the worst case in each mode; a pair below its minimum stops the build.
| Use | Pair | Light | Dark | Minimum |
|---|---|---|---|---|
| tab label | --mp-tabs-fg on --color-bg-primary | 6.73 | 6.39 | 4.5 |
| tab label, hover | --mp-tabs-fg-hover on --mp-tabs-bg-hover | 11.31 | 11.06 | 4.5 |
| selected tab label | --mp-tabs-fg-selected on --color-bg-primary | 13.93 | 14.48 | 7.0 |
| selected-tab indicator | --mp-tabs-indicator on --color-bg-primary | 4.57 | 5.18 | 3.0 |
| pill tab label | --mp-tabs-fg on --mp-tabs-track | 5.32 | 5.01 | 4.5 |
| selected pill tab label | --mp-tabs-fg-selected on --mp-tabs-bg-selected | 13.93 | 14.48 | 7.0 |