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

State History

guide

Every state remembers its value before the last change. previous(), direction() and :changed() turn that into animations that know which way a value moved.

On this page · 6 sections+

previous()

previous(--count) is the value before the last effective change, and the initial value until then. It shares the state’s history, so it costs no extra storage.

direction()

direction(--count) is 1, 0 or -1: whether the last change of a number or dimension state went up, nowhere or down. For other types it is unknown.

:changed()

:changed(--count) matches from the frame after a change until the animations it started have finished, so the same animation replays on every change.

An animated counter

.counter {  --count: state(0);  --last: previous(--count);  /* --count as a position in the strip */  --pos: state(6);}.plus:on(click) {  --count: increment(1);  --pos: increment(1);}.minus:on(click) {  --count: decrement(1);  --pos: decrement(1);}output {  animation-duration: 240ms;}.counter:changed(--count) output {  animation-name: when(    direction(--count) > 0,    slide-up,    slide-down  );}.trail span:nth-var(--pos) {  border-color: oklch(0.55 0.2 35);  color: oklch(0.55 0.2 35);}.trail span:nth-var(--pos) ~ span {  display: none;}
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →
Resultnative buttons
waiting for interaction
state
namevaluepreviouschanges
Waiting for the preview to start.
events
  1. Interact with the preview to record events.

Keyframes

keyframes.css
@keyframes slide-up {  from { translate: 0 40%; opacity: 0; }}@keyframes slide-down {  from { translate: 0 -40%; opacity: 0; }}

Compared with JavaScript

The JavaScript version removes the class, forces a reflow with offsetWidth and adds the class again to restart the animation. :changed() replaces that trick and also waits for the animation to finish before it stops matching.

Tip · Reduced motion

Turn the animation off for people who ask for less motion. The number still updates instantly.

motion.cssreactive
@media (prefers-reduced-motion: reduce) {  .counter:changed(--count) output { animation: none; }}
:changed()Selector · hover any red tokenMatches briefly after an observed change of the nearest state, until the animations it started have finished.Reference →

Limits

  • Only one step of history is kept. A bounded history(--x, n) is planned, not shipped.
  • An infinite animation keeps :changed() matching until the next change.
  • In very large pages, finding the animations a change started costs roughly running animations × page size in Chromium.
Experimental · syntax may change between minor versions