Motion
Like the colour tokens, motion is one shared vocabulary — a fixed set of durations, curves, and springs every animation is built from. JS and CSS are kept in lockstep by a generator and a drift test, so a curve can never mean two things. And because one provider wraps every app, every animation collapses under a reduced-motion preference by construction, not by remembering to.
Durations
Seven speeds, from instant to slow — watch the same distance covered at each. Quick for state, moderate for entrances, slow for ambient motion.
Easings
The system's custom curve set. The dotted diagonal is linear; the solid curve is the token's real acceleration, and the dot rides it at the slow duration.
Four legacy aliases (--ease-standard, --ease-in, --ease-out, --ease-in-out) remain for className utilities, pending migration to the curve set above.
Springs
Eight physics-based springs, raced side by side. One clock releases every dot at once, so the personalities read against each other — stiffness, damping, and mass made visible.
gentle, slow settle
barely-there, professional
balanced default
character with bounce
visible, energetic bounce
quick and responsive
very quick, controlled
instant, zero overshoot
Scales
Four transform steps for interactive feedback — press to shrink, hover to lift. Try them: each chip moves on the real token.
Presets
Semantic compositions — the pairing an animation names instead of re-deriving. Each resolves to the duration and easing, or the spring, it is built from.
Dialogs
Fades
Slides
Hovers
Singles
The rules
A shared vocabulary only holds if it is enforced. These are the four that keep every animation in the system speaking it.
Components animate with the m.* LazyMotion primitives, never the full motion.* namespace, so the animation engine is never forced into a consumer's bundle.
packages/ui/scripts/check-motion.mjsReducedMotionProvider wraps every app root; its MotionConfig reducedMotion="user" means every animation collapses under a reduced-motion preference by construction — no component re-checks the media query.
ReducedMotionProvider (MotionConfig reducedMotion="user")Tokens only — no bespoke curves or durations. The CSS custom properties are generated from motion.ts, and a vitest drift-guard fails the build when the two diverge.
generate-motion-css.mjs + motion-css-sync.test.tsOne orchestrated moment per page — a single staggered entrance, and nothing more. No scroll-triggered animation, no parallax.
conventionIn practice
Adoption looks like this: start from a token, override one axis, and leave a reason.
// packages/ui/src/components/sidebar/sidebar-motion-config.ts
// Based on springs.subtle with slightly reduced mass for a snappier feel.
export const SIDEBAR_TRANSITION = {
...springs.subtle,
mass: 0.8,
} as const;The pieces that speak the language most fluently — the tabs indicator, the segmented control, the sidebar — live in the component catalog.