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

Nested state and cascade

Three nested boxes, each owning --n. Buttons change the nearest owner (or an explicit owner with closest()); a paragraph without its own state reads the nearest owner's value. Labels, chips and markers show which declaration the cascade chose: specificity, a :when() condition, @layer order and !important.

Live previewreal runtime
View source

How it works

  • text:lines 5, 9, 13, 14…Writes one reactive value into the element’s own DOM text: a real text node, never HTML.
  • attr:lines 4, 19, 20, 22…Derives one allowlisted HTML attribute of the matched element, such as disabled or aria-expanded, from a reactive value.
  • :when()line 16Matches elements for which the condition is true, with states resolved from that element.

The difference between runtime state ownership (which cell an action changes, nearest owner, shadowing) and the CSS cascade (which declaration applies to an element), both visible at once.

Left to native HTML and CSS

The cascade itself: specificity, layers, importance, inheritance of custom properties.

What would normally need JavaScript

Per-component state containers and prop drilling.

Limitations

  • :when() has the specificity of one pseudo-class, so equal-specificity ties are decided by source order (as here).
  • Child combinators keep a rule from reaching nested boxes; without them, a rule for the outer box would also match inner boxes.

Accessibility

Plain buttons and text. Not verified with screen readers.

Implementation

2 files · 23 lines
style.cssreactive
/* ---- runtime ownership: every .box owns its own --n; actions change the NEAREST owner --------- */.box { --n: state(0); }.box > .row > .inc { on: click(increment(--n)); }.box > .row > .dec { on: click(decrement(--n)); attr: disabled when(--n <= 0); }   /* ancestor state → descendant attribute */.box > .row > .n { text: var(--n); }/* An explicit owner instead of the nearest one. */.box .outer-inc { on: click(increment(--n, 1, closest(#outer))); }/* No state of its own: var(--n) is inherited from the nearest owner, like any custom property. */.inherit .v { text: var(--n); }/* ---- the cascade decides which declaration applies (not the runtime) --------------------------- *//* Specificity: .box.special is more specific than .box. */.box > .row > .label { text: "base"; }.box.special > .row > .label { text: "specific"; }/* Conditions: a :when() rule is one more selector with the specificity of one pseudo-class. */.box:when(--n >= 3) > .row > .label { text: "three or more"; }/* @layer: a later layer wins over an earlier one, regardless of specificity. */@layer base, override;@layer override { .box > .row > .chip { attr: data-layer "override"; } }@layer base { .box.box.box > .row > .chip { attr: data-layer "base"; } }/* !important wins over higher specificity. */.box > .row > .imp { attr: data-imp "important" !important; }.box.box.box > .row > .imp { attr: data-imp "normal"; }
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Source: the library's examples/nested-cascade/. 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.

Related

History and motion

A level from 0 to 20 with Up, Down, +5 and Reset. The previous value and the direction of the last change are shown as text; the bar width transitions natively; the value pulses (or dips) on every change via :changed(). Reduced motion turns both off.

0.8 syntaxincrement()decrement()set()previous()

Product configurator

Two product cards. Each has a quantity (1–10, buttons and a number field), a color (radio group), gift wrapping (checkbox), a live total (real DOM text, 0.5.1) and a native reset. It is a local UI: no checkout, no network requests, nothing stored.

0.8 syntaxbind:increment()decrement()attr:

Multi-step wizard

Four steps (Introduction, Preferences, Configuration, Review) plus a completion panel, with Back, Next and Finish, a step indicator, a progress bar and a "Step N of 4" status. Two independent instances. Nothing is submitted.

0.8 syntaxfocus:attr:increment()decrement()