Reactive Values
conceptValue functions produce values the stylesheet can’t compute on its own: randomness, the pointer, visibility, the viewport and re-evaluation over time or on events.
On this page · 8 sections+−
Sources at a glance
These are the values’ behavior in ordinary declarations. Inside a mutation of an :on() rule they are read once, at the event: see below.
random() and pick()
random(min, max) gives a value between min and max, of the same type, per element and per call. An optional step makes the result a multiple of the step above min. pick() chooses one of its values.
.confetti { rotate: random(-30deg, 30deg); translate: random(0px, 400px, by 20px) 0; background: pick(#f2542d, #57534e, #d9d5cf);}Two syntaxes
The third value is always a step, never an update interval. To re-roll over time, use live(random(…), 2s).
Native first
Reactive CSS leaves to the browser what the browser can do:
- Native. A top-level call that the browser supports as written, when it gives a value per element, whatever its units or step. Reactive CSS checks this once with a two-element probe, not by browser name.
- Native only. A call with a native sharing key (
--name,element-scoped,property-scoped,fixed <n>) is not claimed at all where the browser supports it: no binding, no diagnostic. Where the browser lacks it, one diagnostic says so; Reactive CSS does not guess what the key means. - Reactive CSS runtime.
by <step>andelement-shared, which browsers reject; calls nested inlive()oron(); calls inside mutations; and any call the browser cannot evaluate.
Browsers without native random() get the Reactive CSS polyfill for the whole list above, following the current draft for the plain step: a zero or negative step is ignored, and max below min gives min. Support differs between browsers and versions; see Browser Compatibility.
--r: random(0, 1) behaves differently by engine. Handled natively, the custom property keeps the call as text, so each element that uses var(--r) rolls its own value. With the polyfill, the element that declares --r gets one value, and its descendants inherit it. To get the same result everywhere, put random() directly in the property that uses it.
Re-evaluating with live() and on()
live() re-evaluates its expression on a timer; random() and pick() inside it re-roll. on() does the same on an event.
.die { --roll: on(click, random(1, 6, by 1)); counter-reset: n var(--roll);}.die::before { content: counter(n);}.greet { --word: on(click, pick("Hello", "Hi", "Hey"));}.greet::after { content: var(--word);}on(event, expr) is a value function that re-evaluates a value. :on(event) in a selector makes an event rule that changes state. Each is unambiguous in its context. The on() value function’s event list includes pointerdown, keyup, animationend, transitionend and toggle, among others.
.particle { translate: live(random(0px, 500px), 2s) live(random(0px, 300px), 2s); transition: translate 1s ease;}mouse()
mouse(x) and mouse(y) return the pointer position in px: viewport coordinates by default, or from the element’s top-left border-box corner with local. Both are 0px before the first pointer move.
.pad { --x: mouse(x, local); --y: mouse(y, local);}.dot { translate: var(--x) var(--y);}Viewport mode is a shared source: one pointer listener for the page and one write per declaration, not per element. local mode measures each element and does not update on scroll alone.
visible()
visible() is 1 when at least threshold (default 0) of the element intersects the viewport, else 0.
.reveal { opacity: calc(0.2 + visible(0.3) * 0.8); transition: opacity 400ms;}viewport()
viewport(width) and viewport(height) compile to native 100dvw and 100dvh, with no runtime. They are a convenience for nesting, as in random(0px, viewport(width)).
Inside mutations
In a mutation of an :on() rule, these values are snapshots taken at the event, not subscriptions:
.die { --face: state(1);}.die:on(click) { --face: set(random(1, 6, 1));}live(), on() and visible() are not available in mutations. Details and the event() fields: Event data.