Skip to content

Tabs

Stable Available in release assets

Organize related sections with an authored ARIA tablist and an optional, dependency-free keyboard and panel coordinator.

Tabs primary example

Show code
<section class="thin-stack">
  <div class="thin-tabs" data-enhance="tabs" role="tablist" aria-label="Product details">
    <button id="details-tab" role="tab" aria-controls="details-panel" aria-selected="true">Details</button>
    <button id="shipping-tab" role="tab" aria-controls="shipping-panel" aria-selected="false">Shipping and returns</button>
    <button role="tab" aria-disabled="true">Unavailable</button>
  </div>
  <section id="details-panel" role="tabpanel" aria-labelledby="details-tab">Product details</section>
  <section id="shipping-panel" role="tabpanel" aria-labelledby="shipping-tab">Shipping and returns</section>
</section>

Each authored example uses the production assets and includes copyable markup.

Anatomy

  • Labelled tablist
  • Native button or link tabs
  • Panels referenced by aria-controls

Variants and sizes

  • Horizontal — shown above
  • Vertical — shown above
  • Automatic activation — shown above
  • Manual activation — shown above

Rendered fixtures exercise authored content and constrained layouts with the same production assets.

  • Selected — shown above
  • Unselected — shown above
  • Disabled — shown above
  • Long labels — shown above
  • Overflowing narrow container — shown above

Interaction and keyboard behavior

Arrow Right/Left moves through horizontal tabs; Arrow Down/Up moves through vertical tabs; Home and End move to the bounds. Automatic activation selects on focus, while data-activation=manual waits for Enter or Space. Disabled tabs are skipped.

Accessibility

Give the tablist an accessible label, use role=tab with aria-controls and aria-selected, and give every panel role=tabpanel with aria-labelledby. Keep all panel content in authored HTML so it remains available before enhancement.

Contract
TABS · APG pattern
Basis
APG Tabs with deliberate roving tabindex and automatic/manual activation
Behavior guarantees
  • Applies the APG Tabs relationships only when each enabled tab controls exactly one panel; unresolved or duplicate targets fail closed so no authored panel is hidden.
  • Synchronizes aria-selected, panel visibility, tabpanel, and aria-labelledby, and restores a surviving tab's focus through refresh.
  • Reconciles tablists after child-list morphs.
Evidence
  • tests/unit/behaviors/tabs.test.ts
  • tests/unit/library/advanced-component-contracts.test.ts

API

Use .thin-tabs on a labelled tablist. Add data-enhance=tabs and call observeNativeBehaviors() from behaviors.js to coordinate the tabs. Set aria-orientation=vertical or data-activation=manual when needed. Replace the authored tabs through a normal DOM update to refresh the behavior.

Behavior tier: dependency-free

Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.

Fallback

Without JavaScript, the authored controls and all ordinary panel content remain in document order; links may be used when navigation is the canonical interaction.

Assets and dependencies

  • /components.css
  • /behaviors.js

Dependencies: None

Source: packages/ui/src/items/tabs/style.ts

Stability evidence

  • Horizontal, vertical, disabled, manual, automatic, and morph-safe keyboard behavior: test:tests/unit/behaviors/tabs.test.ts
  • Native host reconnect contract: test:tests/browser/cdn-starter.spec.ts
  • Cross-theme, density, narrow-container, and long-label review fixture: review:site/src/pages/quality/index.astro#quality-review

Composition

Derived from the examples and style specs, not authored.

Composes