Read the curve.

Motion follows the same rule as a good stroke: deliberate acceleration, clean arrival, no flourish after the result.

Easing

One curve, everywhere. It decelerates into rest—nothing in this system speeds up on the way out.

Putt
cubic-bezier(.4,0,.2,1) · 200ms

Duration

The bigger the object, the longer the move. Nothing in product UI runs past 600ms.

120ms
Hover, focus ring, colour change
200ms
Button press, checkbox, tooltip
320ms
Toast, dropdown, copy confirm
420ms
Panel, drawer, row expand
600ms
Page enter, hero reveal

Patterns

Golf shows up in one place only: the loader. Everywhere else the motion is plain.

Loader · putt in

Full-page and first-load only. Putt, 2.6s loop.

Spinner · inline

Buttons, tables, anything in place. Linear, 900ms.

Copy confirm
Copied

Check strokes on, label swaps. Putt, 320ms.

Response streaming
{
"course": "St Andrews",
"available": 12

Lines rise in at 120ms stagger. Never a typewriter.

Progress · bar

Determinate jobs: bulk export, backfill, key rotation.

Skeleton

Neutral sweep, never green. 1.5s linear.

Toast
Key created

Rises 14px into place. Putt, 320ms.

Button press
Get a key

Scale to 0.965 and back. Putt, 200ms.

Page enter
Every course, one endpoint41,000 courses behind one REST call.

Rise 10px, 80ms stagger. Putt, 600ms.

Rules

  • Motion moves things in from where they came from. Nothing fades in place.
  • One animated element per view. If the loader is running, nothing else moves.
  • The ball appears in the loader and nowhere else. Never in a spinner, never as a cursor, never bouncing.
  • Respect prefers-reduced-motion: swap every animation for an opacity fade at 120ms.

Quick open