Tabs
A tablist of buttons that switches between panels in place; the selected tab carries a 2px indicator bar and bright text, never a fill.
stable · light DOM · 4.0.0. Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
Window gaps, bar height and the focus follows mouse setting.
Profile, font and density.
Bindings for the window manager and the editor.
<j3w1-tabs id="example-tabs-default"><div class="tabs">
<div class="tabs-list" role="tablist" aria-label="Settings sections">
<button class="tabs-tab" role="tab" type="button" id="example-tabs-default-tabs-general" aria-selected="true" aria-controls="example-tabs-default-tabs-panel-general" tabindex="0">General</button>
<button class="tabs-tab tabs-tab-demo-target" role="tab" type="button" id="example-tabs-default-tabs-appearance" aria-selected="false" aria-controls="example-tabs-default-tabs-panel-appearance" tabindex="-1">Appearance</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-default-tabs-keys" aria-selected="false" aria-controls="example-tabs-default-tabs-panel-keys" tabindex="-1">Keys</button>
</div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-default-tabs-panel-general" aria-labelledby="example-tabs-default-tabs-general" tabindex="0">
<p class="tabs-panel-text">Window gaps, bar height and the focus follows mouse setting.</p>
</div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-default-tabs-panel-appearance" aria-labelledby="example-tabs-default-tabs-appearance" hidden="">
<p class="tabs-panel-text">Profile, font and density.</p>
</div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-default-tabs-panel-keys" aria-labelledby="example-tabs-default-tabs-keys" hidden="">
<p class="tabs-panel-text">Bindings for the window manager and the editor.</p>
</div>
</div></j3w1-tabs>overflow
Values live in tokens; this document says what each role means.
Identity and visual character.
Accessibility rules and documented exceptions.
Portability and native fallbacks.
Decision history and current proposals.
Component inventory and coverage.
Declared contrast pairs and evaluation.
Resolved token values by profile.
<j3w1-tabs id="example-tabs-overflow"><div class="tabs">
<div class="tabs-list" role="tablist" aria-label="Open files">
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f1" aria-selected="true" aria-controls="example-tabs-overflow-tabs-panel-f1" tabindex="0">foundations.md</button>
<button class="tabs-tab tabs-tab-demo-target" role="tab" type="button" id="example-tabs-overflow-tabs-f2" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f2" tabindex="-1">identity.md</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f3" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f3" tabindex="-1">accessibility.md</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f4" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f4" tabindex="-1">portability.md</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f5" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f5" tabindex="-1">decisions.md</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f6" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f6" tabindex="-1">inventory.json</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f7" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f7" tabindex="-1">contrast.json</button>
<button class="tabs-tab" role="tab" type="button" id="example-tabs-overflow-tabs-f8" aria-selected="false" aria-controls="example-tabs-overflow-tabs-panel-f8" tabindex="-1">tokens.resolved.json</button>
</div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f1" aria-labelledby="example-tabs-overflow-tabs-f1" tabindex="0">
<p class="tabs-panel-text">Values live in tokens; this document says what each role means.</p>
</div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f2" aria-labelledby="example-tabs-overflow-tabs-f2" hidden=""><p class="tabs-panel-text">Identity and visual character.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f3" aria-labelledby="example-tabs-overflow-tabs-f3" hidden=""><p class="tabs-panel-text">Accessibility rules and documented exceptions.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f4" aria-labelledby="example-tabs-overflow-tabs-f4" hidden=""><p class="tabs-panel-text">Portability and native fallbacks.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f5" aria-labelledby="example-tabs-overflow-tabs-f5" hidden=""><p class="tabs-panel-text">Decision history and current proposals.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f6" aria-labelledby="example-tabs-overflow-tabs-f6" hidden=""><p class="tabs-panel-text">Component inventory and coverage.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f7" aria-labelledby="example-tabs-overflow-tabs-f7" hidden=""><p class="tabs-panel-text">Declared contrast pairs and evaluation.</p></div>
<div class="tabs-panel" role="tabpanel" id="example-tabs-overflow-tabs-panel-f8" aria-labelledby="example-tabs-overflow-tabs-f8" hidden=""><p class="tabs-panel-text">Resolved token values by profile.</p></div>
</div></j3w1-tabs>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/tabs.css';
import '@j3w1/ui/register/tabs';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy tabs --out ./vendor/j3w1/tabsThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
activation | CustomEvent | automatic (default) or manual activation when arrowing among tabs. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
selectedId | string | ID of the selected enabled tab. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
select | (id: string, focus?: boolean): HTMLElement | Select an enabled tab by its exact ID. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-select | CustomEvent | The user selected a tab. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Tabs tabs
A tablist of buttons that switches between panels in place; the selected tab carries a 2px indicator bar and bright text, never a fill.
Report an issue with this component
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
list.bg | color.surface.defaultapproveduse | #0d0707 | D-041 surface.default: color.primitive.modern.panel. |
list.border | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
tab.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
tab.text-selected | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
tab.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
tab.bg-hover | color.interaction.hover.bgapproveduse | #1c1010 | D-041 interaction.hover.bg: color.primitive.modern.hover. |
tab.bg-pressed | color.interaction.pressed.bgobserveduse | #420f0c | Pressed fill. |
tab.indicator | color.border.selected-indicatorobserveduse | #e53935 | 2px indicator bar on the selected tab or nav item. |
tab.indicator-inactive | color.border.selected-indicator-inactiveobserveduse | #9e231f | Indicator bar when the container is not focused. |
tab.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
panel.bg | color.surface.defaultapproveduse | #0d0707 | D-041 surface.default: color.primitive.modern.panel. |
panel.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Default | Overflow |
|---|---|---|
default | Window gaps, bar height and the focus follows mouse setting. Profile, font and density. Bindings for the window manager and the editor. | Values live in tokens; this document says what each role means. Identity and visual character. Accessibility rules and documented exceptions. Portability and native fallbacks. Decision history and current proposals. Component inventory and coverage. Declared contrast pairs and evaluation. Resolved token values by profile. |
hover | ||
focus-visible | ||
selected | ||
selected+focus-visible | ||
selected+container-inactive | ||
disabled |
Purpose
Navigation between peer panels within one view. Between views use sidebar-nav; for exclusive options in a form use radio-group or segmented-control.
Anatomy
Parts as listed in the anatomy. The indicator sits on the rule; tabs carry no fill and no box, and the bar is the only ornament.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | text color.text.default; transparent 2px bottom edge | — |
| hover | background → color.interaction.hover.bg; text unchanged | cursor: pointer |
| focus-visible | 1px dashed color.interaction.focus.ring ring at −2px | the ring |
| selected | text color.text.bright; 2px color.border.selected-indicator bar | aria-selected="true"; the bar; tabindex 0 |
| selected+focus-visible | bar and ring together | both visible |
| selected+container-inactive | bar → color.border.selected-indicator-inactive; text stays bright | lightness drop of the bar; the container’s own inactive state |
| disabled | text color.text.disabled; no hover; bar hidden unless selected | disabled or aria-disabled; cursor: not-allowed |
Precedence: disabled > selected > hover; focus-visible is always drawn.
color.interaction.pressed.bg shows only for the duration of a press.
Keyboard
As in the key table. Automatic activation is the default because panels are cheap; use manual activation when a panel is expensive to render. The panel is a tab stop only when it has no focusable content.
Accessibility
The tablist carries aria-label or aria-labelledby. A vertical tablist
sets aria-orientation="vertical" and swaps the arrow keys. Text meets
4.5:1; the indicator and ring 3:1. Each tab is at least 24×24 CSS pixels in
compact, with 4px between neighbours. The indicator is never the only
signal: aria-selected and the bright text travel with it.
Portability
A toolkit that cannot draw a partial bottom border draws a 2px rule across the full tab width. Hosts with their own tab strip (browsers, editors) keep their keyboard model and take only the colours and the 2px bar.
Non-examples
Pill or filled tabs. A selected tab in the selection fill. Rounded tabs or bar. A strip that wraps to two lines. A selected tab that changes only its text colour. A glow instead of the dashed ring. Every tab in the tab order. Disabling by opacity.
Contrast report
13 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| tabs default text | default | text | color.text.default #e99499 | color.surface.default #0d0707 | 8.72 | 4.5 | pass |
| tabs hover text | hover | text | color.text.default #e99499 | color.interaction.hover.bg #1c1010 | 8.10 | 4.5 | pass |
| tabs focus-visible text | focus-visible | text | color.text.default #e99499 | color.surface.default #0d0707 | 8.72 | 4.5 | pass |
| tabs focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.default #0d0707 | 4.73 | 3 | pass |
| tabs selected text | selected | text | color.text.bright #ffa2a7 | color.surface.default #0d0707 | 10.45 | 4.5 | pass |
| tabs selected border | selected | ui | color.border.selected-indicator #e53935 | color.surface.default #0d0707 | 4.73 | 3 | pass |
| tabs selected+focus-visible text | selected+focus-visible | text | color.text.bright #ffa2a7 | color.surface.default #0d0707 | 10.45 | 4.5 | pass |
| tabs selected+focus-visible border | selected+focus-visible | ui | color.border.selected-indicator #e53935 | color.surface.default #0d0707 | 4.73 | 3 | pass |
| tabs selected+focus-visible outline | selected+focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.default #0d0707 | 4.73 | 3 | pass |
| tabs selected+container-inactive text | selected+container-inactive | text | color.text.bright #ffa2a7 | color.surface.default #0d0707 | 10.45 | 4.5 | pass |
| inactive indicator (decorative; the selected tab keeps aria-selected and bright text) | selected+container-inactive | ui | color.border.selected-indicator-inactive #9e231f | color.surface.default #0d0707 | 2.58 | 1 | pass |
| disabled tab text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.surface.default #0d0707 | 4.48 | 3 | pass |
| tablist bottom rule (decorative) | — | ui | color.border.divider #482b2b | color.surface.default #0d0707 | 1.58 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab | Moves focus to the selected tab (roving tabindex); a second Tab leaves the tablist for the panel. |
| Left / Right | Moves focus to the previous or next tab and wraps; with automatic activation the panel follows. |
| Home / End | Moves focus to the first or last tab. |
| Enter / Space | Activates the focused tab when activation is manual. |
| Delete | Closes the focused tab where tabs are closable (not in this reference). |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: sidebar-nav toolbar segmented-control disclosure
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-OVERFLOW FX-STATE-MATRIX