CSS is Awesome

Interactive component

Tabs

Zero-JS tabs built on native <input type="radio"> + :has() + :nth-of-type(). The browser tracks which tab is active; CSS shows the matching panel. No state machine, no React, no JSON of stories.

0 KB JS for core:has() Baseline Dec 2023Up to 12 tabs default

Overview content — this demo is the real cia.tabs output on real radios. Zero JavaScript: the browser tracks the checked radio, CSS shows the matching panel.
Specs content — the tab list is a <nav> on purpose: panel switching counts panels with :nth-of-type(), so the list must not share their element type.
Reviews content.

Usage

// app.scss
@use 'css-is-awesome' as cia;

.product-tabs { @include cia.tabs; }
<!-- index.html -->
<div class="product-tabs">
  <input type="radio" name="t" id="t1" checked>
  <input type="radio" name="t" id="t2">
  <input type="radio" name="t" id="t3">

  <nav class="cia-tab-list" role="tablist">
    <label for="t1" role="tab">Overview</label>
    <label for="t2" role="tab">Specs</label>
    <label for="t3" role="tab">Reviews</label>
  </nav>

  <div class="cia-tab-panel" role="tabpanel">Overview content</div>
  <div class="cia-tab-panel" role="tabpanel">Specs content</div>
  <div class="cia-tab-panel" role="tabpanel">Reviews content</div>
</div>
MetricciaTailwind + Headless UI
JavaScript shipped0 KB for core~8 KB (Tab.Group)
Active-panel switch:has(input:nth-of-type(N):checked)React state
Keyboard navNative radio (Tab + Arrows + Space)Wired in component
SSRWorks as-is, initial tab via checkedHydrates after render
aria-selected syncOpt-in JS shim (~200 bytes)Wired in component
Theme