Tabs
Two real variants: underline for primary section navigation, segmented for filters. The WAI-ARIA tabs pattern, wired up in vanilla JavaScript — arrows move, Home/End jump to the ends.
Tabs follows the real WAI-ARIA tabs pattern: [role="tablist"] contains
[role="tab"] buttons controlling [role="tabpanel"]s. Those roles are
required for the pattern to be accessible in the first place, so they are the
entire styling contract too — no classes anywhere in this component. The
keyboard wiring (assets/js/system.js) only looks for [data-tabs] /
[role=tab], so the root wrapper keeps that one data attribute and needs
nothing else.
Underline
Mono, with an accent underline. For primary section navigation — a project’s
own tabs, never a filter. This is the default; no data-variant needed.
Markup
<div data-tabs="">
<div role="tablist" aria-label="Repository">
<button role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
<button role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
<button role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
</div>
<div role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
<div role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
<div role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
</div>
Segmented
A pill track; the active segment lifts to the surface. For filters — All ·
Overdue · Upcoming — never primary navigation. data-variant="segmented" on
the tablist is the only attribute either variant needs, since neither has a
natural one of its own.
Markup
<div data-tabs="">
<div role="tablist" data-variant="segmented" aria-label="Filter">
<button role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
<button role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
<button role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
</div>
<div role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
<div role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
<div role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
</div>
Rules
- Never both variants in one view. Underline is for the page’s own sections; segmented is for filtering what is already on screen.
- Segmented is not primary navigation. If it decides which screen the reader is on, it should be underline.
API
| Element / attribute | Values | Notes |
|---|---|---|
[data-tabs] | Required on the root wrapper — what the keyboard wiring in system.js looks for. | |
[role="tablist"] | Required. Contains the tab buttons. | |
[role="tab"] | Required, one per item. aria-selected and tabindex are managed by the JS. | |
[role="tabpanel"] | Required, one per item, referenced by the tab's aria-controls. | |
data-variant | segmented | On the tablist. Omit for the default underline look. |