j3w1 / TabsVue · HTML · React · Astro

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.

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

<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.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@1.1.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

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

StateVisualNon-colour channel
defaulttext color.text.default; transparent 2px bottom edge
hoverbackground → color.interaction.hover.bg; text unchangedcursor: pointer
focus-visiblering 1px dashed color.interaction.focus.ring at −2pxthe ring
selectedtext color.text.bright; 2px color.border.selected-indicator bararia-selected="true"; the bar; tabindex 0
selected+focus-visiblethe bar and the 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. 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.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification