Motion

Durations and easings for every transition, so the whole product moves at one pace.

Live React port

Try it

Pick a duration, a curve and a property. Changing any control replays the move.

150ms · --ease-out--duration-normal
transform
Duration
Ease
Property
Delay
.marker {
  transition: transform var(--duration-normal) var(--ease-out);
}
Delay waits --duration-slower before the move starts.

Durations

Seven steps, from 50 to 500ms. Most of the product moves at 100 or 150ms.

--duration-instant
50ms
--duration-fast
100ms
--duration-normal
150ms
--duration-moderate
200ms
--duration-slow
300ms
--duration-slower
400ms
--duration-slowest
500ms
All seven run on --ease-in-out, so only the duration differs.
1150ms · easeOutCubicThe gauge the product ships.
500ms · --ease-outThe ceiling, on the same arc.
Gauges sweep over 1150ms, past the top of the scale, because a reading is arriving rather than a control responding.

Easings

Six curves. Press Replay to run a dot along each one.

Duration
Slowed down so each curve’s shape shows.
linear
linear
cubic-bezier(0.4, 0, 1, 1)
in
cubic-bezier(0, 0, 0.2, 1)
out
cubic-bezier(0.4, 0, 0.2, 1)
in-out
cubic-bezier(0.34, 1.56, 0.64, 1)
spring
cubic-bezier(0.68, -0.55, 0.265, 1.55)
bounce
Spring and bounce overshoot the end. Bounce also dips below the start.

Composed transitions

Four tokens are whole transition values, each on --duration-normal.

Loops every 2s, slowed to 450ms.
--transition-transformtransform only
Save
Duplicate
Delete
--transition-allevery property, layout included
Save
Duplicate
Delete
/* Same duration. Only one of them moves the rest of the row. */
.save        { transition: var(--transition-transform); }
.save--lazy  { transition: var(--transition-all); }

/* --transition-all: all var(--duration-normal) var(--ease-in-out)
   and `all` includes width, padding and radius — every property
   that forces the elements beside it to be laid out again. */
--transition-all animates width too, so the buttons beside it move. --transition-transform leaves them still.

Usage

  • Use --duration-normal (150ms) for most transitions, and --duration-fast (100ms) for hover colour changes.
  • Keep anything a person acts on at 200ms or less. Use 400ms and 500ms only for skeletons and page transitions.
  • Use --ease-out for things entering, and --ease-in-out for everything else.
  • Use a composed token instead of listing properties, so every component animates the same ones.
  • Keep --ease-bounce for a button’s success and error pops.
  • Don’t use --transition-all. It animates layout, so the things around it move.
  • With reduced motion on, skip the move and show the end state.

Tokens

TokenValueWhere it lands
--duration-instant50ms—
--duration-fast100msHover colour and background changes.
--duration-normal150msThe default. Most component transitions.
--duration-moderate200msDisclosures and dropdowns opening.
--duration-slow300ms—
--duration-slower400msSkeletons and page transitions.
--duration-slowest500msSkeletons and page transitions.
--ease-linearlinear—
--ease-incubic-bezier(.4,0,1,1)—
--ease-outcubic-bezier(0,0,.2,1)Anything entering.
--ease-in-outcubic-bezier(.4,0,.2,1)The default curve.
--ease-springcubic-bezier(.34,1.56,.64,1)Not in use.
--ease-bouncecubic-bezier(.68,-.55,.265,1.55)Button success and error pops.
--transition-colorscolor, background, border · 150msText, fill and border colour.
--transition-opacityopacity · 150msOpacity only.
--transition-transformtransform · 150ms · ease-outTransform only. The one on ease-out.
--transition-allall · 150msEvery property, layout included. Avoid.
(no token)1150ms · easeOutCubicGauge sweep.
(no token)1800msCompass needle settling onto its bearing.