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
run 2a7f · 41s · 412 gb
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 |
don't
do
don't
related
- Brand: the lockup and its one cursor.
- Spinner: the loop for work under way.
- Toast: the one thing that arrives.
- Loading and progress: what to show while something loads.
- Accessibility: reduced motion, among the other settings people bring.
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 withdisplay: none, butdisplaydoesn't apply to SVG animation elements, so the blink carries on. PassshowCursor={false}wherever the lockup stays in view. - Focus.
--focus-ringoutlines appear at once; a field's--cloud-bluefocus border comes in with its 120ms colour change. - Announced, not only moved. A loading data table sets
aria-busy; a spinner is astatusnamed “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.