The problem a token solves
Open any mature codebase and search for cubic-bezier. You will find eleven different curves, three of which differ in the third decimal place, none of which anybody chose deliberately. Search for transition: and you will find 180ms, 200ms, 0.2s, 250ms and 0.3s, all meaning “a short one”.
This is not untidiness. It is the reason the product feels like several products. Consistent motion is one of the strongest signals that an interface was made by one team, and inconsistent motion is noticed before it is identified: people report that something “feels cheap” without being able to say why.
A motion token is the fix: a named value, defined once, referenced everywhere.
What a motion system contains
Three sets of tokens, and a fourth thing that is not a token.
Durations. Three to five, no more.
:root {
--duration-instant: 100ms;
--duration-fast: 180ms;
--duration-normal: 260ms;
--duration-slow: 400ms;
}Easing curves. Four, named by what they are for rather than by their shape.
:root {
/* decelerate: things arriving. Fast start, settle in. */
--ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);
/* accelerate: things leaving. Slow start, fast exit. */
--ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15);
/* both ends: things moving within the screen. */
--ease-move: cubic-bezier(0.2, 0, 0, 1);
/* emphasis: a small overshoot for something that wants attention. */
--ease-snap: cubic-bezier(0.3, 1.4, 0.5, 1);
}Combined recipes. The pairing is the real decision. --motion-sheet-enter: var(--duration-normal) var(--ease-enter) means nobody has to remember which curve goes with which duration.
The fourth thing, which is not a token and must be written down anyway: which property moves. A system that says “sheets use 260ms ease-enter” without saying “sheets translate on Y and fade, they do not scale” will still produce four different sheets.
Naming: by role, not by value
Name a token --duration-fast, not --duration-180. The second one becomes a lie the first time somebody decides 180 was too slow, and then you have --duration-180: 200ms in your codebase, which is worse than a raw number.
Role names also survive platform differences. The same --duration-normal can be 260ms on the web and 300ms on a television, and the calling code does not change.
Why the curves look like that
A cubic-bezier easing function in CSS takes four numbers: the x and y of two control points, with the start fixed at (0,0) and the end at (1,1). The x axis is time, the y axis is progress.
- Both control points low on y early, then rising: slow start, fast finish: acceleration, for exits.
- Both high on y early: fast start, slow finish: deceleration, for entrances.
- A y value above 1: the curve goes past the destination and comes back: overshoot.
- A y value below 0: it pulls backwards first: anticipation.
CSS lets you exceed the 0 to 1 range on y but not on x, because time cannot run backwards. This is why you can make a bounce with a bezier but not a full spring with multiple oscillations; for that you need linear() with sampled points, or a JavaScript spring.
The token nobody defines and everybody needs
Add a reduced-motion variant in the same place:
@media (prefers-reduced-motion: reduce) {
:root {
--duration-instant: 1ms;
--duration-fast: 1ms;
--duration-normal: 120ms;
--duration-slow: 120ms;
--ease-snap: var(--ease-move); /* no overshoot */
}
}Redefining the tokens rather than overriding each component means every component inherits the behaviour, including the ones written next year by somebody who has not read this lesson. This is the single highest-value thing tokens buy you.
Making it real
Tokens only work if the raw values become unusable. In practice:
- A lint rule that fails a build on a literal
cubic-bezier(or a baremsvalue outside the token file. Stylelint does this free withdeclaration-property-value-allowed-list. - One file, in version control, as the only definition. Design tools and code read from the same source if you can manage it; Style Dictionary is the open-source tool that compiles one token file into CSS, iOS, Android and JSON outputs.
- A page in the product that renders every token as a live example. Not a document: a page you can open on a real device and watch. Motion cannot be reviewed in a static specification.
The free path
Nothing here needs a paid tool. CSS custom properties are native. Style Dictionary is Apache-licensed. Stylelint is MIT. Penpot is open source and supports tokens directly. Figma’s variables do the same thing on the free tier for small teams, and the important part (the shared vocabulary) costs nothing either way.
The limitation
Tokens make motion consistent. They do not make it good, and they can entrench a bad decision across an entire product faster than inconsistency ever did. A wrong --duration-normal is now wrong in 400 places.
They also cannot express the thing that matters most in a complex transition: orchestration. Which element moves first, how much the second one overlaps the first, what waits for what. A token file has no vocabulary for that, so it gets encoded in each component separately and drifts anyway. The honest summary is that tokens solve the vocabulary problem and leave the composition problem entirely open.
Every lesson here is open. Your progress is recorded once you pass the Video with AI exam.
Check your understanding
Why is it better to redefine the duration tokens inside a prefers-reduced-motion media query than to add a reduced-motion override to each component?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Deepa, your AI tutor
