Animation Curves & Timing
Easing curves and durations that feel natural — and the tokens to keep them consistent.
Visual DesignIntermediate
Why
Linear easing feels mechanical; ease-in-out feels slow at the ends. Real objects decelerate asymmetrically.
Named easing tokens (standard, emphasized, exit) make every animation feel like the same product.
When
Any app with 3+ animated transitions. Even a few hover states benefit from a shared curve — ad-hoc cubic-bezier per component feels disjoint.
How
1. Standard — ease-out for entering elements (dropdowns, tooltips): starts fast, settles — feels responsive
2. Emphasized — ease-in-out for state changes (expand/collapse): slower, draws attention
3. Exit — ease-in for leaving elements (modal close): starts slow, exits fast — feels dismissed
4. Durations — --dur-fast: 150ms (hover), --dur-standard: 250ms (toggle), --dur-slow: 400ms (page)
5. Spring — for drag/throw (framer-motion spring); CSS can't do true spring, cubic-bezier approximates
6. Distance-scaled — longer distance = longer duration; a 200px slide takes 250ms, a 20px slide 150ms
7. Reduced motion — all these collapse to 0.01ms via the prefers-reduced-motion guard
Example
:root {
/* Easing curves — named by intent, not the bezier numbers */
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* enter: ease-out-ish */
--ease-emphasized: cubic-bezier(0.45, 0, 0.55, 1); /* state change: ease-in-out */
--ease-exit: cubic-bezier(0.4, 0, 1, 1); /* leave: ease-in-ish */
/* Durations — named by scale */
--dur-fast: 150ms; /* hover, focus */
--dur-standard: 250ms; /* toggle, dropdown */
--dur-slow: 400ms; /* page transition, modal */
}
/* Usage — intent is readable */
.dropdown { transition: opacity var(--dur-standard) var(--ease-standard); }
.modal { transition: transform var(--dur-slow) var(--ease-emphasized); }
.toast { transition: opacity var(--dur-fast) var(--ease-exit); }
/* Distance-scaled — longer slide, longer duration */
.slide-200 { transition: transform 250ms var(--ease-standard); }
.slide-20 { transition: transform 150ms var(--ease-standard); }
/* Reduced motion — all collapse */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
Common Problems
- ⚠Linear easing for everything — feels mechanical, no sense of weight or momentum
- ⚠Same duration for a 20px hover and a 200px slide — small motions feel laggy, large ones feel instant
- ⚠No reduced-motion guard — vestibular-sensitive users get every animation at full speed
Best Practices
- ✓Name curves by intent (standard/emphasized/exit) — readable, consistent across the product
- ✓Scale duration with distance — 20px hover 150ms, 200px slide 250ms; small motions stay snappy
- ✓Always include the reduced-motion guard — collapses every duration to 0.01ms