Geometry & Animation
conceptexperimentalelement() reads an element’s own size into CSS. It is experimental. wave() was removed in 0.5.0 in favor of native animation. Everything else geometric stays native.
On this page · 5 sections+−
element() shipped in 0.4.0 as experimental. Its syntax and behavior may still change.
element(width | height)
CSS has no way to read an element’s own size into a value. Container units measure a container for its descendants, and anchor-size() measures an anchor. element(width) and element(height) return the element’s border-box size in px.
.geo-wrap { --size: state(320px);}.geo-wrap .narrower:on(click) { --size: decrement(40px);}.geo-wrap .wider:on(click) { --size: increment(40px);}.geo { --w: element(width); width: var(--size); min-width: 160px; max-width: 100%;}.geo .label { font-size: clamp(12px, calc(var(--w) / 18), 28px);}.geo .px::before { counter-reset: w calc(var(--w) / 1px); content: counter(w) "px wide";}How it works
- One shared
ResizeObserverfor the page, with no layout reads of its own. - Values are written in the next frame, so there is one frame of lag.
- No value (
unset) until the first measurement, so avar()fallback applies. The browser measures before the element’s first paint, and the value is written in the next frame: one frame shows the fallback.display: nonemeasures0px. - Only these two forms are Reactive CSS.
element(#id)stays the native CSS Images 4 image function.
Feedback loops
width: calc(element(width) + 10px) would grow forever. When a size keeps changing in the same direction right after its own writes, writes pause for two frames. If it keeps changing anyway, something else drives it (a transition, a drag) and updates resume. If it stops, it was a loop: the value is frozen and a warning names the element.
wave() was removed
0.4.0 shipped an experimental wave(min, max, duration) for periodic motion. 0.5.0 removed it: it was a convenience over native @property and @keyframes, at a measured style cost of 171–181 ms per second for 1,000 animated elements, where a composited @keyframes animation of translate costs almost nothing. A wave() declaration is now left to the browser, which drops it as invalid.
@property --y { syntax: "<length>"; inherits: false; initial-value: 0px;}@keyframes y { from { --y: -8px; } to { --y: 8px; }}.floating { translate: 0 var(--y); animation: y 1.5s ease-in-out infinite alternate;}Or animate translate directly, which the browser can composite. Add a prefers-reduced-motion rule either way.
Use native CSS for these
Several proposed functions were rejected because native CSS already does the job in Chromium and WebKit.
/* What map() would have done, with native progress() */.stage { --mx: mouse(x, local); }.stage .spot { opacity: progress(var(--mx), 0px, 600px); }Not shipped
history(--x, n), spring(), throttle(), debounce() and element(selector, width) are recorded as planned or deferred. They are not part of 0.9.0. For a spring on discrete changes, a transition with a linear() easing curve already works natively.