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

Dynamic Selectors

concept

Reactive pseudo-classes match from state: a sibling by index, a checkbox after it changes, an element right after a state changes, or a field after it loses focus.

On this page · 6 sections+

:nth-var()

:nth-var(--name) matches the element whose one-based index among its element siblings equals the integer value of --name, read on the parent of the siblings. It has the same specificity as :nth-child().

.tabs {  --active: state(1);}.tab:on(click) {  --active: set(sibling-index());}.panel {  display: none;}.panel:nth-var(--active) {  display: block;}.tab:nth-var(--active) {  border-color: currentColor;}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultvisual only
waiting for interaction

When --name is a state(), the cell is read directly and only the old and new active elements are touched. It also works with a plain custom property, such as .carousel { --active: 3; }.

Note · What it can't see

For plain custom properties, a sibling group is re-checked on DOM and attribute changes, including el.style.setProperty(). Changes that come only from media queries, container queries, animations or stylesheet edits need ReactiveCSS.refresh(). For calc() values, register the property as <integer> with @property.

:just-checked and :just-unchecked

:just-checked matches a checkbox or radio after an observed unchecked → checked change, until its next observed change. It never matches on page load. :just-unchecked is the reverse, including a radio that loses its state when another one in its group is selected.

.task {  --done: state(false);}/* The span next to the checkbox reads the state, so it lives on .task. */.task input:on(change) {  --done: set(event(checked));}.task input:just-checked + span {  animation: pop 300ms;}.task input:just-unchecked + span {  animation: settle 300ms;}.task span {  opacity: when(--done, 0.55, 1);}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultnative checkboxes
waiting for interaction

Why it stays on

There are no timers, so a cancelled or interrupted animation can’t leave a stuck state. On each change the markers swap and the animation starts again. The cost: non-animation declarations such as scale: 1.2 stay applied until the next change, so write those with native :checked.

Caution · Programmatic changes

Setting input.checked = true fires no event. Dispatch new Event('change', { bubbles: true }) afterwards, or call ReactiveCSS.refresh().

:changed()

:changed(--x) matches briefly after an observed change of the nearest state --x: from the frame after the change until the animations and transitions it started in the element’s subtree have finished, or one frame if none started. “It started” means the animations named, and the transitions of properties set, in the :changed() rules: an unrelated animation that restarts in the same frame does not hold it. It never matches on load or after a no-op write.

changed.cssreactive
.cart:changed(--items) .count {  animation: bump 240ms;}
:changed()Selector · hover any red tokenMatches briefly after an observed change of the nearest state, until the animations it started have finished.Reference →

:blur

:blur matches an element from a real focus loss until it is focused again, never on load. Like :just-checked, it marks a moment, so an animation on it plays once per blur:

blur.cssreactive
input:blur:invalid {  animation: shake 250ms;}
:blurSelector · hover any red tokenMatches an element from a genuine focus loss until it is focused again. Unlike :not(:focus), it never matches on page load.Reference →

Use .form:has(:blur) for a container. Details and the difference from :not(:focus): Focus & Blur.

Focus is not a selector here

:focus, :focus-visible and :focus-within stay native. Reactive CSS has no functional :focus(): to move focus when a state becomes true, use the declaration focus: when(condition).

Experimental · syntax may change between minor versions