The guide
How to read a move, where its numbers come from, and how to choose one. Short chapters; the catalogue is the long part.
- 1. What a move is
- 2. The vocabulary and the owners
- 3. Frequency and style
- 4. Entrances and exits
- 5. Reduced motion
- 6. The stage, and how to read a curve
- 7. Composing with worlds
- 8. The scorecard
- 9. Batch 001: Response
1. What a move is
A move is one state change made continuous. The card was at rest; now it is hovered, pressed, selected, loading. Without motion the change happens in one frame. A move spreads it over a few frames so the eye can follow what changed and in which direction. Every move here has a trigger, the properties that change, a duration and a curve, an exit (the way back), a reduced-motion fallback, and a frequency class that says how much style it can afford.
Every move is plain CSS, a transition or keyframes, scoped by the class mo--{id} and driven by data-state on a real card from cardui. Next to it sits the same move written for the Web Animations API, for when the change is driven from script. Nothing is a library: you copy the block and rename the selectors.
2. The vocabulary and the owners
Durations and curves are the Worlds motion tokens, used by name in every template:
| Token | Default | For |
|---|---|---|
--dur-1 | 120ms | micro: hover, focus, toggles, press |
--dur-2 | 240ms | reveal: menus, chips, expand, tooltips, toasts in |
--dur-3 | 400ms | move: sheets, panels, flips, page changes, reorders |
--ease-out | cubic-bezier(.2,.8,.2,1) | entering, and anything that answers a hand |
--ease-in-out | cubic-bezier(.4,0,.2,1) | moving between two places on screen |
--ease-spring | cubic-bezier(.34,1.56,.64,1) | playful overshoot (9.8% of travel); --ease-out in calm worlds |
--ease-exit | cubic-bezier(.4,0,1,1) | leaving; written var(--ease-exit, cubic-bezier(.4,0,1,1)) |
Every number on this site says who owns it. Measured: read from a recording with motioncheck.py, the clip named. Platform: Material, the HIG or Fluent, the page named. Book: one of the six designers of the companion book, named (Malewicz's 10 to 20% overshoot window is the only published number for bounce). Ours: chosen here, with the reason. The tokens themselves are platform and ours: Worlds derived them from Material's short bands and the HIG's sentences. A number with no owner is the median, what you get by not deciding, and check.py refuses it.
3. Frequency and style
Freiberg's test: how often is this done? A move done hundreds of times a day (a press, a hover, a focus ring) is constant: it gets --dur-1 and no style, because any flourish there is paid again on every use. A frequent move (a toggle, a like, a tab change) may take --dur-2 and, rarely, a small overshoot. An occasional move (a drag, a save that succeeds) may take --dur-3. A rare move may spend a flourish. The style field says what the move spends: calm (nothing), standard (tokens only), lively (an overshoot), playful (a flourish). The scorecard's frequency rule holds the two together.
4. Entrances and exits
Three sources, one rule: Material says decelerate in, accelerate out, and make exits shorter; the Google course says the close is the open's opposite; Freiberg says the direction of the exit says where the thing went. So an entrance peaks early (it arrives and settles) and an exit peaks late (it gathers speed and is gone), and the exit is never longer than the entrance. The rule most often broken in the wild is the close that replays the open's curve, which reads as slow.
Two cases are not open and close. A mirror moves between two resting places (a switch's on and off, a tab underline, a stepper's up and down): both ends are arrivals, so both directions keep the same curve. And a landing is an entrance of its own: a dragged card's return is Drop settle, decelerating, because it arrives somewhere new.
5. Reduced motion
Every template contains its own @media (prefers-reduced-motion: reduce) block, and the record says what it does: instant (the state changes in one frame), fade (movement becomes a crossfade) or same (the move stays because the change of state is the news, but loops stop). The stage's Reduced motion switch applies the block, so the fallback is seen, not assumed. The HIG puts it in two words: make motion optional.
6. The stage, and how to read a curve
The stage plays the move on its real card at 420px. Play runs it forward, Reverse runs the exit, Loop plays forward, waits 600ms, plays the exit and repeats. The scrubber holds the move at any millisecond and the speed buttons slow it to a tenth, because every move is replayed through the Web Animations API: the renderer lets the browser build the move's own transitions and keyframes on a hidden copy of the stage, reads their keyframes and timing, and replays them with element.animate(). The card's own state styling that is not part of the move lands at once. The filmstrip is eight copies of the stage, each held at its time; it can crop to what moves so a 2px lift is visible.
The curve graph plots progress against time. Read one thing first: where the speed peaks. At the start, the move lands softly (decelerate, an entrance). At the end, it stops dead (accelerate, an exit). In the middle, it swings (symmetric, a move between places). Nowhere, it is linear (colour and opacity only). Past the line, it overshoots (a spring). The exit is drawn dashed on the same axes, so a shorter exit ends left of the entrance.
7. Composing with worlds
A world from Worlds carries its own motion preset: calm multiplies the three durations by 0.8 and turns --ease-spring into --ease-out, lively multiplies them by 1.2. The tuner's world select applies the world's whole token sheet through cardui's CU.applyWorld, so the move is seen in the world's durations as well as its colours, and Copy CSS inlines that world's values. Copy tokens gives the move's timing as a Worlds-style block, so a world can adopt a measured or platform number by name.
8. The scorecard
tools/check.py scores every move on fifteen rules, the CSS ones ported from motioncheck.py css so the two tools agree: owners, reduced, five seconds (W3C, via the course), five frames (no movement under 80ms), no linear move, no ease-in entrance, no transition: all, no unexplained delay, exit opposite, cheap properties, crowd (at most three elements move at once on a response move), interruptible, tokens, loop off, and frequency. Exit opposite and crowd read the measurement: tools/measure.py runs each move in headless Chrome, samples it at 60 steps and fits the progress to the named curves. Colour and shadow changes are effects, not movement, so the crowd counts what changes position, size or opacity.
9. Batch 001: Response
The first 24 moves are the answers a card gives to a hand: hover, press, focus, select, toggle. All are constant or frequent, so all are calm or standard except three that spend style on purpose: the toggle switch and the like heart overshoot by 10% (Malewicz), and the ripple spends a 400ms flourish on a constant press, which is why it carries two Watches and sits last. Every stage is a real cardui card; where a card lacked the element a move needed (a bookmark on the magazine card, a spinner in a button, a sliding underline under the tabs) the template adds the minimal markup and the recipe says so. Every move reverses mid-flight except the ripple and the button success, which restart. The crowd is at most three (the star rating's three stars), two for most.