Motion & Transition
Easing explorer
Every curve below is a cubic-bezier. Click a thumbnail to inspect it — watch the ball replay the easing, drag the duration, and copy the exact CSS. The two marked (DS) are shipped as tokens; prefer them over ad-hoc curves.
transition: transform 1200ms var(--ease-out);Principles
Animate only transform & opacity
These two are composited on the GPU — they never trigger layout or paint. Animating width, height, top or margin thrashes the main thread and drops frames. Move and fade; don't reflow.
Ease-out for enter, never ease-in
Entering elements should be fast off the mark and settle gently — that's ease-out. Ease-in delays the frames the eye is watching and reads as sluggish at the identical duration.
Easing matters more than duration
A thoughtful curve at 200ms beats a linear tween at any length. Reach for the DS tokens (--ease-out, --ease-in-out) before touching the number.
Exit faster than enter
Dismissal is a decision already made. Play exits at roughly 60% of the enter duration so the UI gets out of the way instead of making the user wait.
Motion must be interruptible
A user can re-open what they just closed. Animations transition from their current state — no waiting for a frame to finish, no janky commit mid-flight.
Animate from the origin
Popovers grow from their trigger; modals scale from center; a swipe follows the finger. Motion that starts where the eye already is feels physical, not decorative.
Purposeful, not decorative
Every animation earns its place: it gives feedback, guides attention, or shows a relationship between states. If it does none of those, cut it.
Respect reduced motion
prefers-reduced-motion means gentler, not off — movement collapses to a fade while functional loops (spinner, skeleton) keep running. Handled globally; components need no extra code.
Durations
The more often a surface is seen, the faster it should animate. Keep UI motion under 300ms — a 200ms dropdown feels more responsive than a 400ms one.
| Surface | Duration | Notes |
|---|---|---|
| Press feedback | 100–160 ms | active:scale on buttons |
| Tooltip | 140 ms | fires constantly — keep it fast |
| Panels | 200 ms | popover, menu, select, hover-card |
| Modals | 240 ms | dialog, alert, profile |
| Exit | ≈ 60% of enter | faster — the user already decided to dismiss |
Reduced motion
Reduced motion means gentler, not off. Under prefers-reduced-motion, panel enter/exit collapse to a plain opacity fade and transform transitions snap to instant — while functional loops (Spinner, Skeleton) keep running. It's handled globally; components need no extra code.
@media (prefers-reduced-motion: reduce) {
.animate-rise, .animate-modal-in { animation: fade-only 0.12s ease both; }
*, *::before, *::after { transition-duration: 0.01ms !important; }
}