skip to content
menu
styles · motion

motion

Movement on our screens is small, quick, and only there to say something: that a control heard you, that a state changed, or that work is under way. Nothing bounces, slides in from far away or plays for show.

colour change
120ms ease · hover, press, check
switch thumb
140ms ease · slides 16px
tile hover
80ms linear · surface, border, chevron
storage-01checked today at 02:00
tooltip
100ms ease · fades in, hides at once
The last backup finished at 02:01.
disclosure caret
120ms ease · turns 90°
toast
160ms ease-out · rises 6px, leaves at once
spinner
4s a lap, while busy
volume
storage-01
storage-02
busy rule
1.1s ease-in-out · runs along the top

The ninth motion, the blinking cursor of the lockup, is in the do and don't further down.

what moves

motion where what it says with reduced motion
Colour change Buttons, icon buttons, text fields, selects, text areas, checkboxes, the switch's track, tabs, filter tabs, the multi select's button, a dialog's close button The control heard you: hover, press, focus, on Unchanged: it is colour, not movement
Tile hover A tile's surface and border change, and the brand chevron fades in This tile is a way in At once
Tooltip Fades in, and hides at once Here is the detail Unchanged
Switch thumb Slides across the track The setting changed Moves at once, without sliding
Disclosure arrow Turns, in a banner's details, a multi select and a data table group Open, or closed At once
Toast Rises a few pixels as it fades in, and leaves at once Something just happened Appears at once
Spinner The gap in its still square frame runs round at a steady pace, without end Working on it Stands still; its label carries the state
Busy line On a data table that is loading, a short blue bar runs along the top edge, again and again This list is being refreshed A still line across the full width
Lockup cursor The cursor block blinks: on for half of each beat, off for the other half The brand's one cursor Keeps blinking; see accessibility

what doesn't move

  • Dialogs, action menus and the multi select's panel open and close at once. Only the multi select's arrow turns.
  • Toasts and tooltips leave at once; only their arrival is animated.
  • Sorting, selecting a row, columns popping in, the filter bar folding up and actions moving into the overflow menu all happen at once. Nothing animates a width, a height or the position of a row.
  • The current tab's underline changes colour in place; it doesn't slide from tab to tab.
  • The keyboard focus outline appears at once. A text field's blue focus border comes in with the usual colour change.
  • Nothing scrolls smoothly: when the focus moves, the page jumps to it.

durations and easing

Most changes take about a tenth of a second: fast enough to feel immediate, and slow enough to be seen. Each has its own easing, the way it speeds up and slows down.

  • 80 ms, at an even speed: the tile, a surface people sweep the pointer across.
  • 100 ms, gently: the tooltip's fade.
  • 120 ms, gently: every other change of state, colours and arrows.
  • 140 ms, gently: the switch's thumb, the one part of a control that travels.
  • 160 ms, slowing down as it lands: the toast, the one thing that arrives.
  • Loops: the spinner, one lap in 4 seconds; the busy line, one run in 1.1 seconds; and the lockup's cursor, one blink in 1.05 seconds. They repeat for as long as they show.

Today: the durations and easings aren't shared values. Each component writes its own, so an app can't read them from the library; match the values above.

how to apply it in your app

  • Answer a person within about 120 ms, and never take longer than 160 ms. A reaction that takes longer feels like lag, not polish.
  • Fade, or move a few pixels, as the toast does. Don't slide panels across the screen, or make things grow on hover.
  • Run a loop only while something is busy, and stop it the moment the work is done. What to show while something loads is in loading and progress.
  • Don't animate the layout. Rows, columns and panels change at once, as the data table's do.
  • Make every movement and every loop respect reduced motion: stop it, or show its end state, as the toast and the busy line do.
  • To see the reduced versions, turn on reduced motion in your system's settings, or have the browser's developer tools pretend it is on.

accessibility

When people turn on reduced motion:

  • These stop: the toast's arrival, the arrows, the tile's hover and its chevron, the switch's thumb, the busy line (it becomes a still line), and the spinner (its frame stands still, three quarters lit, and its label carries the state).
  • These stay the same: the colour changes and the tooltip's fade, which don't move anything.
  • This doesn't stop: the lockup's cursor. The style meant to hold it still doesn't apply to this kind of animation, so it keeps blinking.

And:

  • The lockup's cursor blinks for as long as the page is open. Anything that moves for more than five seconds next to other content needs a way to stop it (WCAG 2.2.2), so wherever the lockup stays in view, turn its cursor off; see brand. On this site, the examples that keep moving have a pause button.
  • Nothing flashes. The only thing that turns on and off is the cursor, less than once a second, far below the limit of three flashes a second.
  • Motion never says something on its own. A loading data table also tells assistive technology that it is busy, a spinner is a status named “loading”, a toast is text that is read out, and a switch announces on or off.

do and don't

do
volume
storage-01
storage-02
Busy where the content is: the rows stay in place under the running line.
don't
A spinner in place of the list: the page jumps when it goes, and again when the rows come back.
do
nalet
In a header that stays on the screen, the lockup holds still.
don't
nalet
A cursor that blinks for as long as the page is open pulls the eye away from the work, and reduced motion doesn't stop it.

specs

motion duration, easing moves reduced motion
Colour change 120ms ease Colours on hover, press, focus and on Unchanged
Tile hover 80ms linear Surface, border; the chevron fades in Instant
Tooltip 100ms ease Fades in; hides at once Unchanged
Switch thumb 140ms ease Slides 16px (14px at sm) transition: none
Disclosure caret 120ms ease Turns 90° (banner details, data table group), 180° (multi select) Instant
Toast 160ms ease-out Rises 6px as it fades in; leaves at once Appears at once
Spinner 4s a lap, linear The gap runs round the still frame Stands still, three quarters lit
Busy rule 1.1s a run, ease-in-out A 2px --cloud-blue bar, 30% wide, along the top edge A still 2px rule, full width
Lockup cursor 1.05s a cycle, without end On for half the cycle, off for the other half Keeps blinking
  • Reduced motion. Every movement and loop goes behind @media (prefers-reduced-motion: reduce).
  • Lockup cursor. Under reduced motion the stylesheet hides the cursor's <animate> element with display: none, but display doesn't apply to SVG animation elements, so the blink carries on. Pass showCursor={false} wherever the lockup stays in view.
  • Focus. --focus-ring outlines appear at once; a field's --cloud-blue focus border comes in with its 120ms colour change.
  • Announced, not only moved. A loading data table sets aria-busy; a spinner is a status named “loading”; a toast is text in a live region; a switch announces on or off.
  • Today: no motion tokens. Each component writes its durations and easings in its own CSS.