Movement should feel calm and intentional — like the brand itself. Confident without urgency. Considered, not flashy.
Motion exists to confirm a change happened — not to entertain. If the user can't tell what changed, the animation has failed. If the animation is the point, it's too much.
Quick on hovers (~120ms), considered on transitions (~200ms), deliberate on overlays (~320ms). Never rushed. Never showy.
Always honor prefers-reduced-motion. Replace movement with a state change, not nothing — the user still needs to see the result.
Big moves (page transitions, hero reveals) should be rare and meaningful. If everything moves, nothing matters.
Four durations. Pick the smallest one that still reads as intentional.
--motion-duration-quick
Hover color shifts, focus rings, small toggles. Should feel imperceptible but confirmed.
--motion-duration-standard
Most component transitions. Tab switches, accordion expand, button pressed → released, card hover lift.
--motion-duration-deliberate
Modal entrance, drawer open, toast slide-in. Long enough to feel composed without dragging.
--motion-duration-slow
Page transitions, hero reveals, large illustrative moments. Use sparingly.
Easing carries personality. Standard for nearly everything; entrance for elements appearing; exit for elements leaving; emphasized only when something needs to pull attention.
cubic-bezier(0.2, 0, 0, 1)
cubic-bezier(0, 0, 0.2, 1)
cubic-bezier(0.4, 0, 1, 1)
cubic-bezier(0.2, 0, 0, 1)
Reusable patterns. Compose by combining a duration and an easing.
opacity 0 → 1
translateY(12px → 0) + opacity 0 → 1
translateX(−24px → 0) + opacity
scale(0.96 → 1) + opacity
scale(1 → 1.4) + opacity 1 → 0
background-position scroll · linear
Hover is the most-frequent motion in the system. Keep it small.
| Surface | Behavior | Tokens |
|---|---|---|
| Button — primary | Background shifts darker (Intentional Blue → Steady Blue) | --btn-primary-bg → --btn-primary-bg-hover |
| Button — secondary | Background fills with ink, label inverts to cream | --btn-secondary-bg-hover |
| Card — default | Background lifts to --surface-muted | --card-bg-hover |
| Card — themed (index) | Inverts to Grounded Black on Clear White | Used on Brand Guide Index |
| Link / nav | 12% Clear White overlay (on dark) · color shift to brand ink (on light) | --state-hover-overlay-on-dark |
| Tag (interactive) | Border color shifts to --ink-default; background unchanged | — |
All hover transitions use --motion-hover (120ms standard easing).
Every component MUST honor prefers-reduced-motion: reduce. The state still has to change — just without the animation.
/* Always wrap motion in a media query */ @media (prefers-reduced-motion: no-preference) { .modal { animation: scale-in var(--motion-overlay-in); } } @media (prefers-reduced-motion: reduce) { .modal { animation: fade-in 0ms; /* state still flips */ } *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }