J3W1 / DESIGN SYSTEM
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 · 1.1.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@1.1.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@1.1.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
Purpose
Switches between peer panels in place without leaving the page. Tabs are navigation within one view; for moving between views use sidebar-nav, for mutually exclusive options inside a form use radio-group or segmented-control.
Anatomy
A tablist strip on color.surface.default with a 1px color.border.divider
rule beneath it; tab buttons in color.text.default; the selected tab in
color.text.bright with a 2px color.border.selected-indicator bottom bar
that sits on the rule; the panel below in color.text.default. Tabs carry no
fill and no box; 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 | ring 1px dashed color.interaction.focus.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 | the bar and the 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.
Pressing a tab uses color.interaction.pressed.bg for the duration of the
press only.
Keyboard
One tab stop for the whole strip: the selected tab has tabindex="0", every
other tab tabindex="-1" (roving tabindex). Left and Right move focus and
wrap; Home and End jump to the ends; Enter and Space activate when activation
is manual. Automatic activation is the default in this theme 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; each tab has
aria-controls pointing at its panel and each panel aria-labelledby
pointing back. Vertical tablists set aria-orientation="vertical" and swap
the arrow keys. Contrast: tab text 8.43:1, selected text 10.10:1, indicator
4.57:1 (≥ 3:1) and ring 4.57:1 on the panel surface. Each tab is at least
24×24 CSS pixels in compact density with 4px between neighbours. The
indicator is never the only signal: aria-selected and the bright text
travel with it.
Portability
A border-bottom that changes colour and an outline are all the drawing needed. Toolkits that cannot draw a partial bottom border draw a 2px rule across the full tab width. Hosts that have 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 drawn with the selection fill. Rounded corners on the tab or the bar. A tab strip that wraps to two lines. A tab that changes only its text colour when selected. A glow instead of the dashed ring. Every tab in the tab order. Disabling by opacity.