Skip to content
Back to Design Hub

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

FAQ