Documents v0.9.0 · previews run the real runtime · experimental features are marked exp

Tabs

Tabs selected by click or arrow keys, with the ARIA tab pattern written from one state.

Live previewreal runtime

Click a tab. Or press Tab to reach the selected tab and use the left and right arrow keys.

How it works

  • state()line 2Makes each matching element the owner of an independent state with an initial value.
  • :on()lines 4, 7, 8An event rule: when the event happens on the matched element (or inside it, for bubbling events), the rule’s state mutations run. The cascade picks one winning mutation per state, per trigger and per element.
  • cycle()lines 7, 8Moves a state to the next value in a list, wrapping around. In an :on() rule it is the mutation --mode: cycle(light, dark).
  • :nth-var()lines 12, 19Matches the sibling whose one-based index equals the integer value of a custom property or state, read on the parent.
  • --attr-*lines 11, 12--attr-<name>: <value> writes one allowlisted HTML attribute of the matched element from a reactive value: ARIA states such as aria-expanded, aria-checked or aria-disabled, disabled, hidden, tabindex or data-*. The only custom-property namespace with DOM semantics.
  • focus:lines 15, 16, 17Moves focus to the matched element when its value goes from false to true because of a user action. In 0.9 this declaration is the focus request: focus: when(--open).

A complete tab set from one number state. A click writes the tab’s position, arrow keys cycle it, and every visible and accessible detail reads it: the selected tab, its aria-selected and tabindex, the panel, and the focus.

Native CSS alternative

None for the selection itself. Native HTML has no tab element; <details name> gives an exclusive accordion, not tabs.

Limitations

  • The number of tabs is written in the stylesheet (cycle(1, 2, 3) and one focus rule per tab).
  • Home and End are not handled.

Implementation

2 files · 19 lines
style.cssreactive
/* ---- Reactive CSS -------------------------------------------------------------------------- */.tabs { --active: state(1); }.tabs .tab:on(click) { --active: set(sibling-index()); }/* Arrow keys move the selection, wrapping at the ends. prevent() keeps the page from scrolling. */.tabs [role="tablist"]:on(keydown(ArrowRight), prevent()) { --active: cycle(1, 2, 3); }.tabs [role="tablist"]:on(keydown(ArrowLeft), prevent()) { --active: cycle(3, 2, 1); }/* The ARIA tab pattern: one selected tab, the only one in the Tab order. */.tabs .tab { --attr-aria-selected: false; --attr-tabindex: -1; }.tabs .tab:nth-var(--active) { --attr-aria-selected: true; --attr-tabindex: 0; color: var(--ink); border-bottom-color: currentColor; }/* Focus follows the selection after a key press. */.tabs .tab:nth-child(1) { focus: when(--active == 1); }.tabs .tab:nth-child(2) { focus: when(--active == 2); }.tabs .tab:nth-child(3) { focus: when(--active == 3); }.tabs .panel:nth-var(--active) { display: block; }
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Source: site/src/catalog/interfaces/tabs/. style.css from its Reactive CSS divider; the layout above it and the shared api.css run but are not shown.

Related

Dropdown menu

A menu that opens on click and closes on Escape, on a choice or on a click anywhere else.

experimentalstate()outside():on()attr()--attr-*focus:

Theme switcher

One keyword state cycles through three themes, and the page colors and the button label follow it.

experimentalstate()cycle():when()content

Signup form

A text field, a select and a checkbox feed a live preview, and Reset clears both.

experimentalstate():on()event()content:when()