Motion is quiet: a small scale, used sparingly

Animation in the interface is functional, not decorative: it confirms an interaction, reveals structure, or signals loading. The one thing that moves for its own sake is the background, which is scenery rather than interface and keeps its own timings. The whole interface vocabulary is 9 durations and 5 easing curves, defined once as --motion-* tokens and shared by the component library and this site. Never write a literal 0.2s ease in component CSS. Compose a duration token with an easing token instead. Click any tile to replay its animation.

Durations: core scale

Four steps cover day-to-day UI. Base is the workhorse: the standard for colour, opacity, and border transitions. Step down to fast for hover feedback, up to slow for structural change, and reserve slower for page entrances. Each tile animates at its token's actual speed.

Fast--motion-duration-fast150ms
Base--motion-duration-base200ms
Slow--motion-duration-slow300ms
Slower--motion-duration-slower600ms
  • FastQuick feedback: hovers, icons, tooltips, nav links
  • BaseThe default: colour, opacity, and border transitions
  • SlowStructural change: accordion, sidebar width, toast enter
  • SlowerDeliberate page-entrance reveals

Durations: extended

Special-purpose timings that would otherwise live as magic numbers. They are named so they stay consistent, but they are not part of the day-to-day scale. Reach for the core four first.

Instant--motion-duration-instant75ms
Deliberate--motion-duration-deliberate400ms
Loop spin--motion-duration-loop-spin1000ms
Loop shimmer--motion-duration-loop-shimmer1800ms
Loop matrix--motion-duration-loop-matrix1400ms
  • InstantBelow the threshold where a change reads as motion: the Composer growing a line
  • DeliberateCarousel slide
  • Loop spinSpinner rotation
  • Loop shimmerSkeleton shimmer, agent label sweep
  • Loop matrixAgent dot-matrix cycle (twelve slots)

Easing curves

Five named curves, each with a distinct job. Every tile animates over the same 600ms so the shape of the curve is what you see: standard for everyday transitions, emphasized for layout, entrance for anything appearing on screen, linear for loops, spring for the one sanctioned overshoot.

Standard--motion-ease-standardease
Emphasized--motion-ease-emphasizedcubic-bezier(0.4, 0, 0.2, 1)
Entrance--motion-ease-entrancecubic-bezier(0.16, 1, 0.3, 1)
Linear--motion-ease-linearlinear
Spring--motion-ease-springcubic-bezier(0.34, 1.56, 0.64, 1)
  • StandardGeneral default for colour and opacity transitions
  • EmphasizedSize and layout changes: sidebar width, accordion height
  • EntranceExpressive decelerate for enter animations: modals, dropdowns, toasts
  • LinearContinuous motion: spinner, progress bars
  • SpringPlayful overshoot: toggle switch thumb

Reduced motion

The token layer respects prefers-reduced-motion at the source: when the preference is set, every duration token collapses to 0.01ms and a universal guard flattens any remaining hardcoded transitions and animations. Components that use the tokens get accessibility for free: there is nothing to opt into, and no component-level media queries to write.

That guard is CSS, so it cannot reach animation driven from JavaScript. The background field runs on the GPU through a frame loop, which means it has to check the preference itself. It does: with reduced motion set, it draws a single still frame and never starts animating.