8.1 The principle
Since Labs is calm software for people who are busy. Animation is feedback, not personality. If a movement is there to be admired rather than to explain a change of state, cut it.
The test: would a person notice if this were instant? If the answer is no, make it instant. If the answer is "they would be confused about what just happened", that is what the motion is for.
8.2 The scale
| Token | Duration | Easing | Use |
|---|---|---|---|
| Instant | 0ms | — | State that must read as a fact, not an animation |
| Fast | 120ms | cubic-bezier(0.2, 0, 0, 1) | The default. Colour, opacity, border on hover and focus |
| Base | 200ms | same | Drawers, disclosure, position changes |
| Slow | 320ms | same | Full-surface transitions. The ceiling |
Nothing in this brand animates longer than 320ms. One easing curve throughout — an ease-out that starts immediately and settles. No bounce, no elastic, no spring.
8.3 What animates
| Property | Verdict |
|---|---|
color, background-color, border-color | ✅ The overwhelming majority of motion in the product |
opacity | ✅ Fades, disclosure |
transform: translate | ✅ Drawers and sheets only |
box-shadow | ✅ Card hover, sparingly |
width, height, top, left | ❌ Layout-thrashing. Use transform |
rotate, scale | ❌ Except a loading spinner |
Prefer transition-colors over transition-all. transition-all animates
properties you did not intend and produces the jelly effect that makes an
interface feel cheap.
8.4 Patterns
Hover — 120ms colour change. Never a size change; a control that grows under the cursor moves the thing you were about to click.
Focus — the ring appears instantly. Never fade a focus ring: a keyboard user tabbing quickly must see where they are without waiting.
Drawer / mobile sidebar — 200ms translateX, backdrop fades over the same
duration.
Modal — appears instantly. It is a mode change, and a fade makes the whole app feel slow.
Loading — a 1s linear spin for a spinner. For content, prefer a skeleton in
surface-mist with a 2s pulse over a spinner, and prefer nothing over a
skeleton if the wait is under ~300ms.
Route change — nothing. The page changes. Page transitions are a personality we have not earned.
8.5 Reduced motion
Non-negotiable. Ships in tokens/since-brand.css:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Anything driven by JavaScript must check the same signal:
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
Reduced motion means the end state, immediately — never a missing state.
8.6 Do and don't
Do
- ✅ 120ms colour transitions as the default
- ✅ Instant focus rings and instant modals
- ✅ One easing curve everywhere
- ✅ Honour
prefers-reduced-motion
Don't
- ❌
transition-all - ❌ Animate anything past 320ms
- ❌ Bounce, spring, or elastic easing
- ❌ Scale a control on hover
- ❌ Add a page transition