Desktop
Desktop motion
The desktop's shared springs, window and menu variants, and how they step down to fades under reduced motion and in performance mode.
Every desktop surface moves on the same few presets, so windows, menus, widgets and icons feel like one material: ui/motion/motion-presets.ts for windows, sheets and menus, and ui/desktop/desktop-motion.ts for the desktop, Finder and widgets, both on motion/react. Import them rather than writing new springs. They animate opacity and transforms only, so they stay on the compositor; nothing animates left, top, width or height. These sit beside the site-wide motion tokens (see Motion).
Springs and timings#
| Preset | Values | For |
|---|---|---|
springs.snappy |
Stiffness 560, damping 36, mass 0.7 | Menus, buttons, the + panel: small things that should feel instant but alive |
springs.window |
Stiffness 380, damping 32, mass 0.9 | Windows opening and restoring, phone sheets settling |
springs.gentle |
Stiffness 220, damping 28, mass 1 | Larger, softer settles: widgets, panels, selection pops |
easings.out |
cubic-bezier(0.16, 1, 0.3, 1) |
Anything arriving |
easings.in |
cubic-bezier(0.4, 0, 1, 1) |
Anything leaving |
easings.inOut |
cubic-bezier(0.65, 0, 0.35, 1) |
The minimize pour |
durations |
instant 0, fast 120 ms, base 200 ms, slow 360 ms |
Fades, and the pour |
SPRING_SNAPPY |
Stiffness 520, damping 34, mass 0.6 | Hover, press, sliding highlights, the widget drawer |
SPRING_SETTLE |
Stiffness 380, damping 30, mass 0.8 | An icon settling into its cell after a drop or a Clean up |
SPRING_SOFT |
Stiffness 260, damping 28, mass 0.9 | Soft entrances: icons, widgets, panels, empty states |
FADE_FAST |
140 ms, cubic-bezier(0.32, 0.72, 0, 1) |
View switches, kept brief so keyboard users never wait |
Windows#
windowVariants take the window's motion as custom: whether motion is reduced, where it grows from and where it minimizes to.
| Moment | Motion |
|---|---|
| Open | Grows from where it was launched (the last pointer press, if within 1.2 s, or an icon's centre) from 55%, or from its own centre at 92% when opened from the keyboard, on springs.window; opacity in 120 ms |
| Close | Scales to 94% and fades, 160 ms, easings.in |
| Minimize | Pours into its WINDOWS entry: width 100 → 55 → 4%, height 100 → 80 → 4%, opacity 1 → 0.9 → 0, over 360 ms on easings.inOut, with the middle step at 55% of the time. Hidden only once it has played |
| Restore | The pour in reverse, out of the same point |
| Focus | The shadow deepens over 220 ms; an unfocused window's title dims to 62% over 180 ms |
| Phone sheet | Slides up from the bottom edge on springs.window; leaves downwards in 220 ms |
| A reload | Windows it reopens appear without an entrance; a frame remounted for a window already on screen does not grow in again |
The main window pours the same way through the Web Animations API, from where it stood.
Menus and panels#
| Preset | Motion |
|---|---|
popoverVariants |
From 94% and 2 px up to rest on springs.snappy, opacity in 100 ms, its entries staggered 14 ms apart after 20 ms; out to 97% with a 100 ms fade. Grows from the pointer, or from its row for a submenu |
listItemVariants |
Each entry drops in 3 px and fades in over 140 ms (easings.out); out in 60 ms |
fadeScale |
From 96% on springs.snappy; out to 98% in 120 ms |
fadeOnly |
Opacity only: in 120 ms, out 80 ms. The reduced-motion fallback |
staggerChildren(step, max) |
Children 20 ms apart by default, capped so long lists never crawl |
| The + panel | Rises out of the + from 92% and 8 px lower on springs.snappy, its sections 20 ms apart; out to 96% in 100 ms |
| Widget drawer | Slides in on SPRING_SNAPPY; away in 180 ms |
| WINDOWS entries | Settle in from 6 px to the left over 260 ms |
The desktop#
| Moment | Motion |
|---|---|
| Icons appear | Rise 6 px from 86% on SPRING_SOFT, 25 ms apart, the delay capped at 300 ms |
| Widgets appear | Rise 12 px from 97% on SPRING_SOFT, 45 ms apart, capped at 400 ms |
| A panel or empty state | Rises 8 px from 98% on SPRING_SOFT |
| An item opens | Its glyph pulses to 116% and back over 320 ms |
| Finder changes folder | Going in zooms from 96.5%, going back from 103.5%; switching views cross-fades in place |
| Hover on tiles and widgets | A 2 px lift over 200 ms |
| An icon is dropped | It springs into its cell on SPRING_SETTLE |
Reduced motion and performance mode#
useReducedMotionSafe()is true when the OS asks for reduced motion or the app runs in performance mode, which setsMotionConfig reducedMotion="always". Then every preset fades only, briefly (pickVariantsswaps infadeOnly): windows, sheets, menus and panels appear and leave by opacity.- Performance mode is the default; Full effects (Settings › Appearance › Visual effects) brings movement and the blur back (see Wallpaper & glass).
- Under the OS setting, the CSS motion steps down too: window shadows and title dimming take 1 ms, WINDOWS entries fade in over 120 ms, and hover lifts and pressed lights stay still.
- Keep information in the end state, never only in the animation.
- The exceptions: teammates' live cursors keep their smoothing in performance mode (a cursor that jumps several times a second is hard to follow) and move only transforms; they snap to their latest place only when the OS asks for reduced motion. The World Clock's hands keep running in performance mode for the same reason, and stop under reduced motion.
Guidelines#
Use springs.window for anything window-sized, springs.snappy for menus and controls, and the reveal variants for what appears on the desktop.
Behaviour: motion follows the pointer (windows grow from the press that opened them, menus from the click, minimizing pours into the sidebar), so the member can always see where a thing came from and went.
Don't animate layout, write a spring of your own, play an entrance on reload, or carry meaning only in motion.