Motion and Transitions
Date: 2026-08-17
Movement that explains what happened. Its job is continuity — showing where something came from and went — and any motion that doesn’t do that job is decoration paid for in time and, for some people, in nausea.
Motion in an interface communicates change: what appeared, what left, what moved where, and how things relate.
The test for any animation: does it explain something? If the interface would be equally understandable without it, it’s decoration — which is permissible, and should be cheap and skippable.
What motion is for
CONTINUITY a modal growing from the
button that opened it
→ shows the relationship
CAUSALITY an item flying to the
basket
→ connects action to result
SPATIAL MODEL a panel sliding from the
right
→ establishes where it
lives
ATTENTION a subtle pulse on a
changed value
→ directs the eye
FEEDBACK a button depressing
→ confirms the tap
— Feedback and System Status
See: Feedback and System Status
Continuity is the highest-value use. Something appearing instantly from nowhere has to be located and understood; something that grows from the thing you clicked is already explained.
Duration
UNDER 100ms imperceptible — may as
well be instant
100–200ms small changes: hovers,
button states, toggles
200–300ms most transitions: modals,
panels, expansions
300–500ms large movements, full-page
transitions
OVER 500ms feels slow. Almost never
right in a transactional
interface
Motion has a cost measured in the user’s time, paid on every interaction. A 400ms transition on something used twenty times a session costs eight seconds of waiting for the same information.
Exits should be faster than entrances. The person has decided to dismiss it; they don’t need to watch it leave.
Easing
Covered in depth in Easing and Duration. The essential:
LINEAR mechanical, wrong for
almost everything
EASE-OUT fast start, slow finish
→ entrances. The default
choice
EASE-IN slow start, fast finish
→ exits
EASE-IN-OUT both
→ movement between two
on-screen positions
Ease-out for anything entering is the single most useful rule — it arrives quickly and settles, which reads as responsive.
Performance
ANIMATE
transform translate, scale, rotate
opacity
← compositor-only, cheap
AVOID ANIMATING
width, height, top, left, margin
← trigger layout on every frame
— The Rendering Pipeline
Animating left instead of transform: translateX() is the most common performance mistake in interface motion, and on a mid-range phone the difference is visible jank.
Animation that causes layout shift is doubly costly — it looks bad and it affects Core Web Vitals — Cumulative Layout Shift.
Reduced motion is not optional
Vestibular disorders make large or parallax motion genuinely nauseating, and the operating system carries the preference.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Respect it, and don’t remove feedback entirely — replace movement with an instant state change rather than with nothing, or the interface loses its causality cues for exactly the people who need them clearest — Reduced Motion.
Where it goes wrong
- Motion on every element. A page where everything fades in on scroll is slower to read and harder to scan
- Blocking motion. Content that can’t be read until an animation finishes
- Scroll-hijacking. Overriding native scroll is disorienting and frequently breaks accessibility
- Auto-playing carousels, which move content away as people read it and are ignored past the first slide
- Motion masking slowness. A 600ms transition covering a 600ms load is honest; a transition added because the load is slow is a bandage
- Inconsistent durations. The same kind of transition should take the same time everywhere — Design Tokens
Tokenise it
duration.fast 150ms
duration.base 250ms
duration.slow 400ms
easing.entrance cubic-bezier(0, 0, .2, 1)
easing.exit cubic-bezier(.4, 0, 1, 1)
Motion is a design system concern, not a per-component decision. Values chosen per component drift, and the inconsistency reads as sloppiness without anyone identifying why.