prop-for-that: CSS reacts, JS just listens
pointer.x
pointer.y
00
00 / auto
Quick start: just an attribute
import 'prop-for-that/auto' once, then tag any element<br>with data-props-for and list the props you want.
data-props-for="size"
 × 
JS<br>import 'prop-for-that/auto'<br>HTML<br>div data-props-for="size" style="resize: both">div><br>CSS<br>.box::after {<br>counter-reset: w calc(var(--live-w)) h calc(var(--live-h));<br>content: counter(w) ' × ' counter(h);
01
01 / pointer
Track the pointer
Bind pointer-local to an element and it gets its own<br>--live-local-pointer-x-ratio / --live-local-pointer-y-ratio props.
local
move your<br>pointertilt your<br>device
the panel follows, then rests
Enable tilt
Motion blocked · needs a mouse here
HTML<br>div id="card" data-props-for="pointer-local">…div><br>CSS<br>.tilt-card {<br>--rx: calc((.5 - var(--live-local-pointer-y-ratio)) * 16deg);<br>--ry: calc((var(--live-local-pointer-x-ratio) - .5) * 16deg);<br>transform: rotateX(var(--rx)) rotateY(var(--ry));
02
02 / scroll
Reveal once, then stay
Triggered, not linked. --const-has-entered latches once a<br>panel is fully in view, so each reveals once and stays. Scroll back up: a<br>view() timeline would reverse, this won’t.
HTML<br>article class="reveal" data-props-for="visibility">…article><br>CSS<br>.reveal {<br>opacity: calc(.25 + var(--const-has-entered) * .75);<br>translate: 0 calc((1 - var(--const-has-entered)) * 1.5rem);<br>transition: .5s;
scroll the steps ↓
Each step develops in as it enters — lift, unblur, fade — then holds.
ii
--live-visible lights this card only while it’s wholly on screen.
iii
One element, two signals — live toggles, entered latches.
in view<br>entered
iv
Scroll back up: in view dims, entered stays. No reset.
03
03 / range
One value, many readers
Bind one source to the container; the gauge ring, the number, and<br>the slider all read the same value. At either end, a<br>@container style() query flips a min / max<br>state that var() can’t express.
and<br>writes --live-value / --live-value-pct HERE, so both the gauge<br>(a separate element) and the slider inherit the value. -->
value
←→
HTML<br>div id="meter" data-props-for="range"><br>input type="range" min="0" max="100" value="42"><br>div><br>CSS<br>.gauge__num::after {<br>counter-reset: v calc(var(--live-value));<br>content: counter(v);<br>.gauge__arc {<br>background: conic-gradient(var(--accent)<br>calc(var(--live-value-pct) * 360deg), var(--line) 0);<br>Discrete state: a style query, not var()<br>@container style(--live-value: 100) {<br>.gauge__num { color: var(--max-tint); }<br>.gauge__flag::after { content: 'max'; }
04
04 / style query
Discrete state, whole rules
var() interpolates a number; it can’t turn<br>3 into the word “high” or switch a rule on at<br>one exact value. @container style() can: each level<br>lights a different block of CSS, zero JS branches.
and<br>writes --live-value (0–4) HERE, so the word and the stops below<br>inherit it and can @container style() the exact integer. -->
level
↑↓
offlowmidhighmax
HTML<br>div id="level" data-props-for="range"><br>input type="range" min="0" max="4" step="1" value="2"><br>div><br>CSS<br>@container style(--live-value: 0) {<br>.level__word::after { content: 'Off'; }<br>@container style(--live-value: 4) {<br>.level__word::after { content: 'Max'; }<br>.level__core { --tier-h: 25; }
05
05 / field
Length, live as you type
The limits stay in HTML (minlength,<br>maxlength); CSS reads the live length against them to<br>fill the meter, count up, and name the state. No keystroke handler.
short bio<br>min 12 · max 120
HTML<br>div id="note" class="field" data-props-for="field" style="--min: 12; --max: 120"><br>textarea minlength="12" maxlength="120">textarea><br>div><br>CSS<br>.field__meter i {<br>inline-size: calc(var(--live-length) / var(--max) * 100%);<br>@container style(--live-length: 120) {<br>.field__word::after { content: 'max reached'; }
06
06 / trig
Yes, CSS can do trigonometry
Each pupil finds the cursor with atan2(), then rides<br>there on cos() and sin(). The trigonometry<br>is all in the stylesheet; JS never computes an angle.
HTML<br>div class="eye" data-props-for="pointer-local">…div><br>CSS<br>.eye {<br>--a: atan2(<br>calc(var(--live-local-pointer-y-ratio) - .5),<br>calc(var(--live-local-pointer-x-ratio) - .5));<br>.eye__pupil {<br>translate:<br>calc(cos(var(--a)) * 1.1rem)<br>calc(sin(var(--a)) * 1.1rem);
07
07 / clock
A clock, ticking in pure CSS
The clock source ticks --live-seconds once a second; the<br>hands rotate by calc() and the readout is a CSS counter.<br>The only JavaScript is one setInterval.
HTML<br>div class="clock" data-props-for="clock">…div><br>CSS<br>.clock__hand--sec {<br>rotate: calc(var(--live-seconds) * 6deg);<br>.clock__hand--hour {<br>rotate: calc(var(--live-hours) * 30deg<br>+ var(--live-minutes) * .5deg);
08
08 / globals
Ambient device state
The quiet globals: online, link speed, battery, frame rate. Each<br>writes on :root for CSS to read. Toggle offline in<br>DevTools to watch it flip. (Battery and Network are...