Animation & Motion System
Duration, easing, and spring tokens that make motion consistent, purposeful, and reduced-motion safe.
Design TokensIntermediate
Why
Ad-hoc transitions (300ms here, 150ms there, linear easing) feel random and can trigger vestibular issues. A motion scale with semantic easings makes motion purposeful and respects prefers-reduced-motion.
Tokens Studio (formerly Figma Tokens) has 500k+ installs, and the W3C Design Tokens Format Module is nearing candidate recommendation. Tokens enable dark mode, multi-brand theming, and platform parity from a single source.
When
Define motion tokens when you have 3+ animated components. If you're pasting cubic-bezier strings ad hoc, consolidate them into named easings immediately.
How
1. Duration scale — instant=100, fast=150, normal=250, slow=400, slower=600 (ms)
2. Easings — ease-out for entrances, ease-in for exits, ease-in-out for state changes
3. Spring tokens — for physics-based: stiffness=300, damping=30 (framer-motion)
4. Semantic motion — fade, slide-up, scale-in, each = duration + easing + property
5. Reduced motion — wrap in @media (prefers-reduced-motion: reduce) → duration: 0
6. Tokens — --duration-{fast|normal|slow}, --ease-{out|in|in-out}
7. Stagger — children delay = index × 50ms, capped at 300ms total
Example
/* motion-tokens.css */
:root {
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}
/* Semantic motion — entrance */
.fade-in { animation: fade-in var(--duration-normal) var(--ease-out); }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
/* Reduced motion — disable all animation */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
// framer-motion — spring with reduced-motion guard
const motion = useReducedMotion() ? { duration: 0 } : { type: "spring", stiffness: 300, damping: 30 };
<motion.div animate={{ opacity: 1 }} transition={motion} />
Common Problems
- ⚠Linear easing for UI — feels mechanical and cheap; use ease-out for entrances
- ⚠No reduced-motion guard — vestibular users get nauseous from parallax/spin
- ⚠300ms everywhere — too slow for hover, too fast for page transitions, no semantic distinction
Best Practices
- ✓Use ease-out for entrances, ease-in for exits — matches user expectation of motion
- ✓Always respect prefers-reduced-motion — it's WCAG 2.3.3 and a 5-line CSS guard
- ✓Cap stagger at 300ms total — beyond that, the sequence feels slow and theatrical