j3w1 / theme

j3w1 / theme4.0.0

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Workbench
  • SPEC Form builder
  • SPEC Task kit
  • SPEC Recipes
  • SPEC Figma bridge
  • SPEC Report an issue
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • SPEC Specification
  • DEMO Vue application demo

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.

Markup
<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.

Markup
<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.0
import '@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.

Vue SFC, React, Astro and plain HTML integration →

Copy the complete implementation

npx --package=@j3w1/ui@4.0.0 j3w1-ui copy tabs --out ./vendor/j3w1/tabs

The 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

NameType / signatureMeaning
disabledCustomEventDisable the native controls owned by this instance.
loadingCustomEventMark the primary control busy and prevent duplicate activation.
activationCustomEventautomatic (default) or manual activation when arrowing among tabs.

properties

NameType / signatureMeaning
disabledbooleanDelegates disabled state to native children.
namestringNative control name; application form keys remain explicit.
selectedIdstringID of the selected enabled tab.

methods

NameType / signatureMeaning
refresh(): voidReconnect behavior after replacing native child structure.
focus(options?: FocusOptions): voidFocus the first usable native control.
select(id: string, focus?: boolean): HTMLElementSelect an enabled tab by its exact ID.

events

NameType / signatureMeaning
j3w1-selectCustomEventThe 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

  • Family: Navigation
  • Maturity: stable
  • Priority: R1
  • Since: 0.1.0
  • Custom widget: APG tabs (tablist, tab, tabpanel) with roving tabindex — APG pattern
  • specified
  • demonstrated
  • test implemented

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

Open component workbench

Tokens

Focus a role to inspect its values; follow its link for persistent usage and source details.

PartRoleValueUse
list.bgcolor.surface.defaultapproveduse#0d0707D-041 surface.default: color.primitive.modern.panel.
list.bordercolor.border.dividerapproveduse#482b2bD-041 border.divider: color.primitive.modern.divider.
tab.textcolor.text.defaultapproveduse#e99499D-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-selectedcolor.text.brightapproveduse#ffa2a7D-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-disabledcolor.text.disabledapproveduse#a3676bD-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-hovercolor.interaction.hover.bgapproveduse#1c1010D-041 interaction.hover.bg: color.primitive.modern.hover.
tab.bg-pressedcolor.interaction.pressed.bgobserveduse#420f0cPressed fill.
tab.indicatorcolor.border.selected-indicatorobserveduse#e539352px indicator bar on the selected tab or nav item.
tab.indicator-inactivecolor.border.selected-indicator-inactiveobserveduse#9e231fIndicator bar when the container is not focused.
tab.ringcolor.interaction.focus.ringobserveduse#e539351px dashed focus ring on controls and rows.
panel.bgcolor.surface.defaultapproveduse#0d0707D-041 surface.default: color.primitive.modern.panel.
panel.textcolor.text.defaultapproveduse#e99499D-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).

StateDefaultOverflow
default

Window gaps, bar height and the focus follows mouse setting.

Values live in tokens; this document says what each role means.

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

StateVisualNon-colour channel
defaulttext color.text.default; transparent 2px bottom edge—
hoverbackground → color.interaction.hover.bg; text unchangedcursor: pointer
focus-visible1px dashed color.interaction.focus.ring ring at −2pxthe ring
selectedtext color.text.bright; 2px color.border.selected-indicator bararia-selected="true"; the bar; tabindex 0
selected+focus-visiblebar and ring togetherboth visible
selected+container-inactivebar → color.border.selected-indicator-inactive; text stays brightlightness drop of the bar; the container’s own inactive state
disabledtext color.text.disabled; no hover; bar hidden unless selecteddisabled 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.

PairStateKindForegroundBackgroundRatioMinResult
tabs default textdefaulttextcolor.text.default #e99499color.surface.default #0d07078.724.5pass
tabs hover texthovertextcolor.text.default #e99499color.interaction.hover.bg #1c10108.104.5pass
tabs focus-visible textfocus-visibletextcolor.text.default #e99499color.surface.default #0d07078.724.5pass
tabs focus-visible outlinefocus-visibleuicolor.interaction.focus.ring #e53935color.surface.default #0d07074.733pass
tabs selected textselectedtextcolor.text.bright #ffa2a7color.surface.default #0d070710.454.5pass
tabs selected borderselecteduicolor.border.selected-indicator #e53935color.surface.default #0d07074.733pass
tabs selected+focus-visible textselected+focus-visibletextcolor.text.bright #ffa2a7color.surface.default #0d070710.454.5pass
tabs selected+focus-visible borderselected+focus-visibleuicolor.border.selected-indicator #e53935color.surface.default #0d07074.733pass
tabs selected+focus-visible outlineselected+focus-visibleuicolor.interaction.focus.ring #e53935color.surface.default #0d07074.733pass
tabs selected+container-inactive textselected+container-inactivetextcolor.text.bright #ffa2a7color.surface.default #0d070710.454.5pass
inactive indicator (decorative; the selected tab keeps aria-selected and bright text)selected+container-inactiveuicolor.border.selected-indicator-inactive #9e231fcolor.surface.default #0d07072.581pass
disabled tab text (exempt; house floor 3:1)disableduicolor.text.disabled #a3676bcolor.surface.default #0d07074.483pass
tablist bottom rule (decorative)—uicolor.border.divider #482b2bcolor.surface.default #0d07071.581pass

Keys

KeyAction
TabMoves focus to the selected tab (roving tabindex); a second Tab leaves the tablist for the panel.
Left / RightMoves focus to the previous or next tab and wraps; with automatic activation the panel follows.
Home / EndMoves focus to the first or last tab.
Enter / SpaceActivates the focused tab when activation is manual.
DeleteCloses 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