motion
nothing has mass. three durations, one curve, and only color, opacity and position ever move.
three durations, one curve
| token | value | use |
|---|---|---|
--fast | 120 ms | color and state changes: hover, pressed, selected, focus |
--base | 200 ms | things that move a short way: the toggle dot, a menu opening, the accordion triangle |
--slow | 320 ms | things that arrive: a toast rising, a dialog appearing, a page section loading in |
--ease | cubic-bezier(.2, 0, 0, 1) | movement only. it starts quickly and settles. color changes are linear |
in motion
a color change in 120 ms, linear
the dot travels 42 px in 200 ms, eased
hover: a shape shifts 12 px and comes back. position, never scale
loading: the three shapes blink in turn on a 1.2 s cycle. nothing rotates
arrival: translate up and fade in, together
rules
- only three properties move.color, opacity and position. never scale, blur, rotation or shadow. a thing that grows is pretending to come closer, and nothing on a screen is closer.
- movement is short.12 px for a nudge, 42 px for a toggle, the height of the element for an arrival. a long slide is a film, and the interface is not a film.
- no bounce, no overshoot.nothing has mass. the easing settles; it does not spring.
- one thing at a time.a dialog appears, then its content is simply there. no staggered lists, no cascades.
- reduced motion is honored.with prefers-reduced-motion, every transition is removed and the loader stops blinking. the toast still appears; it just does not rise.
- motion confirms, it never decorates.if removing the animation changes nothing the person needs to know, remove it.
in css
.ws-btn { transition: background var(--fast); } /* color: linear */
.ws-toggle::before { transition: left var(--base) var(--ease); } /* movement: eased */
.ws-toast[data-show] { animation: ws-rise var(--slow) var(--ease); } /* arrival */
@keyframes ws-rise { from { transform: translateY(12px); opacity: 0 } to { transform: none; opacity: 1 } }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important } }