Settings panel
A notifications section whose channel options are disabled while notifications are off, a theme and text-size preview, an "Advanced settings" disclosure, and a native reset. Nothing is saved.
Two independent tab sets. Each tab reports a real aria-selected, inactive panels are hidden, one tab is disabled in the HTML, and the "Billing" tab is disabled until a checkbox in the first panel is checked.
| name | value | previous | changes |
|---|---|---|---|
| Waiting for the preview to start. | |||
Selection state owned by the tab set and derived onto many elements: aria-selected on every tab, hidden on every panel, disabled on one tab. In 0.5.2 this was impossible: a state could only bind an attribute of its owner.
role="tab", role="tabpanel", aria-controls, aria-labelledby are static markup.hidden hides panels natively; [aria-selected="true"] and :disabled style the tabs.Setting aria-selected on the tabs and hidden on the panels for every selection change, and enabling the Billing tab. A radio-button CSS trick can show one panel, but it cannot expose tab semantics.
tabindex (attr: tabindex "0" on the selected tab, "-1" on the others): only the selected tab is in the Tab order, and Tab moves on to its panel. ArrowLeft/ArrowRight (wrapping), Home and End select a tab and move focus to it (key() + focus:). The disabled tabs are skipped; Billing joins the arrow order when enabled. Tested in Chromium and WebKit, except Tab itself, which is checked in Chromium only: WebKit’s default Tab order skips buttons (macOS setting). Not verified with screen readers, so compliance is not claimed beyond the tested keyboard behavior. Manual activation (arrows move focus without selecting) is not expressible: focus follows selection here.prevent(), so a scrollable page does not jump when you move between tabs. Tested with real keys on a scrollable page: Home/End in both engines, arrows in Chromium. Headless WebKit does not scroll on arrow keys at all, so there only defaultPrevented is checked.--active is a position (sibling-index()). Reordering tabs in the HTML without reordering the panels breaks the pairing.--billing became false, the active tab would be disabled. Here the checkbox lives in the Profile panel, so that cannot happen. A general fix needs a rule that resets --active.aria-selected on every tab, real hidden on inactive panels: hidden panels are out of the tab order and the accessibility tree (tested).disabled on Team (static) and Billing (derived): announced as unavailable, skipped by Tab.tabindex are implemented and tested (0.6.0). Missing: Delete (closable tabs, not applicable), screen-reader verification./* ---- state: the active tab's position (1-based), owned by each tab set ------------------------ */.tabs { --active: state(1); --billing: state(false); }.tabs [role=tab] { on: click(set(--active, sibling-index())); }/* ---- derived attributes (attr:, 0.5.3) --------------------------------------------------------- Every tab reports aria-selected; the cascade gives "true" to the active one. Every panel is hidden except the active one. Child combinators keep a nested tab set independent. */.tabs > [role=tablist] > [role=tab] { attr: aria-selected "false"; attr: tabindex "-1"; focus: false; }.tabs > [role=tablist] > [role=tab]:nth-var(--active) { attr: aria-selected "true"; attr: tabindex "0"; focus: true; }/* ---- keyboard (0.6.0): the ARIA Tabs pattern with automatic activation ---------------------------- Roving tabindex (above): only the selected tab is in the Tab order. Arrow keys, Home and End select another tab, and focus: moves focus to it (a keydown is a user action). prevent() (0.7.0) stops these keys from also scrolling the page; other keys (PageDown, Space…) keep scrolling. The key lists skip disabled tabs; they are written per tab set because each set has its own tabs. */#account > [role=tablist] { on: key(ArrowRight, prevent(), cycle(--active, 1, 2)), key(ArrowLeft, prevent(), cycle(--active, 2, 1)), key(Home, prevent(), set(--active, 1)), key(End, prevent(), set(--active, 2)); }#account:when(--billing) > [role=tablist] { on: key(ArrowRight, prevent(), cycle(--active, 1, 2, 3)), key(ArrowLeft, prevent(), cycle(--active, 3, 2, 1)), key(Home, prevent(), set(--active, 1)), key(End, prevent(), set(--active, 3)); }#docs > [role=tablist] { on: key(ArrowRight, prevent(), cycle(--active, 1, 2)), key(ArrowLeft, prevent(), cycle(--active, 2, 1)), key(Home, prevent(), set(--active, 1)), key(End, prevent(), set(--active, 2)); }[role=tabpanel]:focus-visible { outline-offset: 4px; }.tabs > .panels > [role=tabpanel] { attr: hidden true; }.tabs > .panels > [role=tabpanel]:nth-var(--active) { attr: hidden false; }/* "Team" is disabled in the HTML (static). "Billing" is disabled until the checkbox in the Profile panel is checked: a derived, native disabled. */.tabs .has-billing { bind: --billing; }.tabs .billing { attr: disabled when(--billing, false, true); }Source: the library's examples/accessible-tabs/. style.css from its first section comment; the layout above it runs but is not shown. Written in the 0.8 syntax (on:, bind:, text:, attr:), which 0.9.0 still runs; see Migrating from 0.8 for the 0.9 forms.
A notifications section whose channel options are disabled while notifications are off, a theme and text-size preview, an "Advanced settings" disclosure, and a native reset. Nothing is saved.
Three cart lines with − / + / Remove, a subtotal per line, the item count and total, an "empty cart" message, and Checkout and "Empty cart" buttons that are disabled while the cart is empty. Local only: Checkout does nothing.
A collapsible list of three sample notifications. Each can be marked read (a toggle with aria-pressed) or dismissed. "Undo dismiss" restores the last dismissed one, "Dismiss all" clears the list, and the unread count updates as real text.