{"course":{"slug":"motion-and-audio","title":"Motion, Sound and Music","url":"https://addaly.com/learn/motion-and-audio","level":"intermediate","track":"create"},"lesson":{"slug":"motion-tokens","title":"Naming the motion so it stops drifting","url":"https://addaly.com/learn/motion-and-audio/motion-tokens","position":15,"of":72,"minutes":8,"answer":"Motion tokens are named by role rather than by value so the number can change without the name becoming a lie.","keyIdea":"Motion tokens are named by role rather than by value so the number can change without the name becoming a lie, and redefining them inside a reduced-motion query is the cheapest way to make every future component inherit the right behaviour."},"body":{"format":"markdown","text":"## The problem a token solves\n\nOpen 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\".\n\nThis 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.\n\nA motion token is the fix: a named value, defined once, referenced everywhere.\n\n## What a motion system contains\n\nThree sets of tokens, and a fourth thing that is not a token.\n\n**Durations.** Three to five, no more.\n\n```css\n:root {\n  --duration-instant: 100ms;\n  --duration-fast:    180ms;\n  --duration-normal:  260ms;\n  --duration-slow:    400ms;\n}\n```\n\n**Easing curves.** Four, named by what they are for rather than by their shape.\n\n```css\n:root {\n  /* decelerate: things arriving. Fast start, settle in. */\n  --ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);\n  /* accelerate: things leaving. Slow start, fast exit. */\n  --ease-exit:  cubic-bezier(0.3, 0, 0.8, 0.15);\n  /* both ends: things moving within the screen. */\n  --ease-move:  cubic-bezier(0.2, 0, 0, 1);\n  /* emphasis: a small overshoot for something that wants attention. */\n  --ease-snap:  cubic-bezier(0.3, 1.4, 0.5, 1);\n}\n```\n\n**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.\n\nThe 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.\n\n## Naming: by role, not by value\n\nName 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.\n\nRole 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.\n\n## Why the curves look like that\n\nA 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.\n\n- Both control points low on y early, then rising: slow start, fast finish: acceleration, for exits.\n- Both high on y early: fast start, slow finish: deceleration, for entrances.\n- A y value above 1: the curve goes past the destination and comes back: overshoot.\n- A y value below 0: it pulls backwards first: anticipation.\n\nCSS 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.\n\n## The token nobody defines and everybody needs\n\nAdd a reduced-motion variant in the same place:\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  :root {\n    --duration-instant: 1ms;\n    --duration-fast:    1ms;\n    --duration-normal:  120ms;\n    --duration-slow:    120ms;\n    --ease-snap: var(--ease-move); /* no overshoot */\n  }\n}\n```\n\nRedefining 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.\n\n## Making it real\n\nTokens only work if the raw values become unusable. In practice:\n\n- A lint rule that fails a build on a literal `cubic-bezier(` or a bare `ms` value outside the token file. **Stylelint** does this free with `declaration-property-value-allowed-list`.\n- 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.\n- 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.\n\n## The free path\n\nNothing 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.\n\n## The limitation\n\nTokens 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.\n\nThey 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."},"check":{"question":"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?","options":[{"text":"Because custom properties defined inside a media query take priority over component rules regardless of specificity, which is the only way to reliably override a third-party component.","correct":false,"tempting":"Invents a specificity rule; custom properties follow ordinary cascade and inheritance, and the benefit is coverage rather than priority."},{"text":"Because components written later inherit the behaviour without their authors having to know the rule exists, so the coverage does not decay as the codebase grows.","correct":true},{"text":"Because the media query is evaluated once at page load, so the browser can skip creating compositor layers for every animation in the document and save GPU memory.","correct":false,"tempting":"Media queries are live rather than evaluated once, and layer creation is unrelated to how the duration was set."},{"text":"Because per-component overrides would need a JavaScript listener on the matchMedia object, and a listener that is registered per component will leak memory as components mount and unmount over a long session.","correct":false,"tempting":"Describes a real hazard of the JavaScript approach, but CSS overrides per component need no listener at all, so this is not the reason."}]},"prev":{"slug":"lower-thirds-that-work","title":"Why your lower-third looks like a template","url":"https://addaly.com/learn/motion-and-audio/lower-thirds-that-work"},"next":{"slug":"waiting-states","title":"Spinners, skeletons and honest waiting","url":"https://addaly.com/learn/motion-and-audio/waiting-states"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Naming the motion so it stops drifting', in Motion, Sound and Music. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/motion-and-audio/motion-tokens (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Naming the motion so it stops drifting","courseTitle":"Motion, Sound and Music","module":{"index":2,"slug":"motion-that-carries-meaning","title":"Motion that carries meaning"},"answer":null,"keyIdea":"Motion tokens are named by role rather than by value so the number can change without the name becoming a lie, and redefining them inside a reduced-motion query is the cheapest way to make every future component inherit the right behaviour.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-problem-a-token-solves","runs":[{"text":"The problem a token solves"}]},{"t":"p","runs":[{"text":"Open any mature codebase and search for "},{"text":"cubic-bezier","code":true},{"text":". You will find eleven different curves, three of which differ in the third decimal place, none of which anybody chose deliberately. Search for "},{"text":"transition:","code":true},{"text":" and you will find 180ms, 200ms, 0.2s, 250ms and 0.3s, all meaning “a short one”."}]},{"t":"p","runs":[{"text":"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."}]},{"t":"p","runs":[{"text":"A motion token is the fix: a named value, defined once, referenced everywhere."}]},{"t":"h2","id":"what-a-motion-system-contains","runs":[{"text":"What a motion system contains"}]},{"t":"p","runs":[{"text":"Three sets of tokens, and a fourth thing that is not a token."}]},{"t":"p","runs":[{"text":"Durations.","b":true},{"text":" Three to five, no more."}]},{"t":"code","lang":"css","label":"CSS","text":":root {\n  --duration-instant: 100ms;\n  --duration-fast:    180ms;\n  --duration-normal:  260ms;\n  --duration-slow:    400ms;\n}"},{"t":"p","runs":[{"text":"Easing curves.","b":true},{"text":" Four, named by what they are for rather than by their shape."}]},{"t":"code","lang":"css","label":"CSS","text":":root {\n  /* decelerate: things arriving. Fast start, settle in. */\n  --ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);\n  /* accelerate: things leaving. Slow start, fast exit. */\n  --ease-exit:  cubic-bezier(0.3, 0, 0.8, 0.15);\n  /* both ends: things moving within the screen. */\n  --ease-move:  cubic-bezier(0.2, 0, 0, 1);\n  /* emphasis: a small overshoot for something that wants attention. */\n  --ease-snap:  cubic-bezier(0.3, 1.4, 0.5, 1);\n}"},{"t":"p","runs":[{"text":"Combined recipes.","b":true},{"text":" The pairing is the real decision. "},{"text":"--motion-sheet-enter: var(--duration-normal) var(--ease-enter)","code":true},{"text":" means nobody has to remember which curve goes with which duration."}]},{"t":"p","runs":[{"text":"The fourth thing, which is not a token and must be written down anyway: "},{"text":"which property moves","b":true},{"text":". 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."}]},{"t":"h2","id":"naming-by-role-not-by-value","runs":[{"text":"Naming: by role, not by value"}]},{"t":"p","runs":[{"text":"Name a token "},{"text":"--duration-fast","code":true},{"text":", not "},{"text":"--duration-180","code":true},{"text":". The second one becomes a lie the first time somebody decides 180 was too slow, and then you have "},{"text":"--duration-180: 200ms","code":true},{"text":" in your codebase, which is worse than a raw number."}]},{"t":"p","runs":[{"text":"Role names also survive platform differences. The same "},{"text":"--duration-normal","code":true},{"text":" can be 260ms on the web and 300ms on a television, and the calling code does not change."}]},{"t":"h2","id":"why-the-curves-look-like-that","runs":[{"text":"Why the curves look like that"}]},{"t":"p","runs":[{"text":"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."}]},{"t":"ul","items":[[{"text":"Both control points low on y early, then rising: slow start, fast finish: acceleration, for exits."}],[{"text":"Both high on y early: fast start, slow finish: deceleration, for entrances."}],[{"text":"A y value above 1: the curve goes past the destination and comes back: overshoot."}],[{"text":"A y value below 0: it pulls backwards first: anticipation."}]]},{"t":"p","runs":[{"text":"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 "},{"text":"linear()","code":true},{"text":" with sampled points, or a JavaScript spring."}]},{"t":"h2","id":"the-token-nobody-defines-and-everybody-needs","runs":[{"text":"The token nobody defines and everybody needs"}]},{"t":"p","runs":[{"text":"Add a reduced-motion variant in the same place:"}]},{"t":"code","lang":"css","label":"CSS","text":"@media (prefers-reduced-motion: reduce) {\n  :root {\n    --duration-instant: 1ms;\n    --duration-fast:    1ms;\n    --duration-normal:  120ms;\n    --duration-slow:    120ms;\n    --ease-snap: var(--ease-move); /* no overshoot */\n  }\n}"},{"t":"p","runs":[{"text":"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."}]},{"t":"h2","id":"making-it-real","runs":[{"text":"Making it real"}]},{"t":"p","runs":[{"text":"Tokens only work if the raw values become unusable. In practice:"}]},{"t":"ul","items":[[{"text":"A lint rule that fails a build on a literal "},{"text":"cubic-bezier(","code":true},{"text":" or a bare "},{"text":"ms","code":true},{"text":" value outside the token file. "},{"text":"Stylelint","b":true},{"text":" does this free with "},{"text":"declaration-property-value-allowed-list","code":true},{"text":"."}],[{"text":"One file, in version control, as the only definition. Design tools and code read from the same source if you can manage it; "},{"text":"Style Dictionary","b":true},{"text":" is the open-source tool that compiles one token file into CSS, iOS, Android and JSON outputs."}],[{"text":"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."}]]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"Nothing here needs a paid tool. CSS custom properties are native. Style Dictionary is Apache-licensed. Stylelint is MIT. "},{"text":"Penpot","b":true},{"text":" 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."}]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"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 "},{"text":"--duration-normal","code":true},{"text":" is now wrong in 400 places."}]},{"t":"p","runs":[{"text":"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."}]}],"check":{"question":[{"text":"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?"}],"options":[{"text":[{"text":"Because custom properties defined inside a media query take priority over component rules regardless of specificity, which is the only way to reliably override a third-party component."}],"correct":false,"tempting":[{"text":"Invents a specificity rule; custom properties follow ordinary cascade and inheritance, and the benefit is coverage rather than priority."}]},{"text":[{"text":"Because components written later inherit the behaviour without their authors having to know the rule exists, so the coverage does not decay as the codebase grows."}],"correct":true,"tempting":null},{"text":[{"text":"Because the media query is evaluated once at page load, so the browser can skip creating compositor layers for every animation in the document and save GPU memory."}],"correct":false,"tempting":[{"text":"Media queries are live rather than evaluated once, and layer creation is unrelated to how the duration was set."}]},{"text":[{"text":"Because per-component overrides would need a JavaScript listener on the matchMedia object, and a listener that is registered per component will leak memory as components mount and unmount over a long session."}],"correct":false,"tempting":[{"text":"Describes a real hazard of the JavaScript approach, but CSS overrides per component need no listener at all, so this is not the reason."}]}],"order":[2,1,3,0],"why":null},"prev":{"slug":"lower-thirds-that-work","title":"Why your lower-third looks like a template","minutes":10},"next":{"slug":"waiting-states","title":"Spinners, skeletons and honest waiting","minutes":9}}}