Motion presets
Twelve ways things are allowed to move here, each rendered live, so a name has something to point at.
Every number on this page is a token: three durations, three easings and two springs from the scale, and one stagger. A preset is a name for what may move, how far and on which of those, and the two decisions left are which preset and whether to animate at all. The page transition between the principles is the thirteenth, and the only one you have to leave this page to see.
The knobs under each stage retune it, on that stage alone, and the readout is the lines that would make the change permanent in the scale. Nothing here is remembered: a value worth keeping is worth committing. Then turn on reduced motion in your system settings and open each one again: every preset has a form without travel, and what it does then is part of what it is. The skill that owns this set, its rules and its check is mxa-motion.
rise, rise-group
One thing entering, or text and lists entering together. On the real page a group waits until it is scrolled into view, and rises once.
- moves
- opacity 0 → 1, translate 0.5rem → 0; per line or item, never per letter
- default
- base, ease-out; stagger token between items, counted to eight
Text arrives a line at a time,
never a letter at a time,
and the stagger is one token, forty milliseconds,
counted to eight and then no further,
so a long list lands within half a second
rather than a line a second later.
--duration-base: 200ms; --motion-stagger: 40ms; --motion-rise: 0.5rem; --ease-out: var(--ease-out);
Last of eight lands at 480ms. The cap is slow + base, 520ms.
settle
A container changing size: a button growing a label, a card expanding. The difference is animated as a transform and the text inside is never scaled.
- moves
- the box, as a transform; content fades in after it has moved
- default
- spring-settle: damping 0.8, stiffness 380, settles in 443ms
/* spring "settle": damping ratio 0.8, stiffness 380; settles in 443ms, overshoots 1.5% */ --ease-spring-settle: linear(0, 0.0147, 0.0533, 0.1089, … 49 stops); --duration-spring-settle: 443ms; content fades in after 120ms (--duration-fast)
Copy gives the full 48-stop easing. Motion reads the same two numbers: stiffness 380, damping 31.2. Under 1 overshoots; 1 is critical and never does. Settles in 443ms.
settle, in width
Width and height changing in different kinds of element: a field that widens on focus, a row that grows as things are added, two panes sharing the room. None of them transitions a dimension.
- moves
- each box, as a transform; siblings shift as transforms too
- default
- spring-settle, the same numbers as above
/* spring "settle": damping ratio 0.8, stiffness 380; settles in 443ms, overshoots 1.5% */ --ease-spring-settle: linear(0, 0.0147, 0.0533, 0.1089, … 49 stops); --duration-spring-settle: 443ms;
All three run on the settle spring, settling in 443ms. None of them transitions width or height: Motion measures before and after and animates the difference as a transform, which is why the text never stretches.
settle, for an indicator
A segmented control or tab bar. One element moves between the options rather than each option lighting up.
- moves
- one highlight, between positions
- default
- spring-snap: damping 0.9, stiffness 700, settles in 317ms
/* spring "snap": damping ratio 0.9, stiffness 700; settles in 317ms, overshoots 0.2% */ --ease-spring-snap: linear(0, 0.0138, 0.0496, 0.1006, … 49 stops); --duration-spring-snap: 317ms;
The same spring drives pop, menus and tooltips. Settles in 317ms; Motion damping 47.6.
reveal-height
Disclosure, accordion, show more. The one layout transition in the set, bounded to base.
- moves
- grid rows 0fr → 1fr; content fades in after
- default
- base, ease-out; fade delayed by fast
The build compresses until it runs continuously underneath the work. The calendar does not follow it: an interview still takes an hour because a person is talking, and exploration takes as long as it takes to be surprised.
--duration-base: 200ms; --duration-fast: 120ms; --ease-out: var(--ease-out);
The box takes base; the content fades in after fast. The one layout transition in the set, which is why base is its ceiling.
flip-icon
A chevron turning over, a hamburger becoming a cross, a sort arrow. The icon starts and ends on screen, so it eases in and out.
- moves
- rotate, and for the hamburger a translate and a fade on the same clock
- default
- fast, ease-in-out
--duration-fast: 120ms; --ease-in-out: var(--ease-in-out);
Ease-in-out, because the icon starts and ends on screen. A spring is available here and almost always wrong: an icon that overshoots looks broken, not lively.
press, hover
Every interactive thing, all the time. The two smallest presets, and the two that are already on everything.
- moves
- hover: colour only; press: scale 1 → 0.97, straight back on release
- default
- fast, ease-out; press returns instantly
--motion-press: 0.97; --duration-fast: 120ms;
Hold a button down to judge the press. The return is instant on purpose; a press that eases back reads as a bounce.
swap
A skeleton giving way to content, a tab panel, an image loading. Same place, different content.
- moves
- a crossfade; the outgoing takes 2px of blur to hide the mismatch two layouts always have
- default
- base in, ease-out; fast out, ease-in
--duration-base: 200ms; /* in */ --duration-fast: 120ms; /* out */ blur on the outgoing: 2px
The outgoing leaves faster than the incoming arrives, always. The blur hides the pixel-level mismatch two layouts have; past about 3px it reads as an effect.
pop
A popover. Opens from the trigger's side, and Base UI's own attributes drive it: the class is the whole preset.
- moves
- opacity 0 → 1, scale 0.95 → 1 from the trigger's side
- default
- in on spring-snap; out fast, ease-in
/* spring "snap": damping ratio 0.9, stiffness 700; settles in 317ms, overshoots 0.2% */ --ease-spring-snap: linear(0, 0.0138, 0.0496, 0.1006, … 49 stops); --duration-spring-snap: 317ms; --duration-fast: 120ms; /* out */ --motion-pop: 0.95; /* scale it opens from */
Never from 0. A thing that appears from a point reads as a cartoon; under about 0.9 it starts to.
pop, as a tooltip
The smallest popup, so the same spring reads faster. The delay before it opens is the one number here that is not motion.
- moves
- the same as pop
- default
- the same as pop, after a delay
/* spring "snap": damping ratio 0.9, stiffness 700; settles in 317ms, overshoots 0.2% */ --ease-spring-snap: linear(0, 0.0138, 0.0496, 0.1006, … 49 stops); --duration-spring-snap: 317ms; --duration-fast: 120ms; /* out */ --motion-pop: 0.95; /* scale it opens from */ delay before it opens: 600ms (a prop, not a token)
A tooltip is the smallest popup and moves the least: the same spring reads faster on a box this size. The delay is the one number that is not motion, and it is what makes tooltips bearable.
slide
A sheet or a drawer, and the page transition. One axis, and the backdrop only fades.
- moves
- translate along one axis; backdrop opacity
- default
- slow in, ease-out; base out, ease-in
--duration-slow: 320ms; /* in */ --duration-base: 200ms; /* out */ --ease-out: var(--ease-out); /* in */ --ease-in: var(--ease-in); /* out */
The sheet comes in on slow and leaves on base; the backdrop follows the same clock. Try spring-settle on the way in and ease-in on the way out, which is the pair most sheets end up with.
shake
An invalid field, a refused action. Three oscillations and done; it does nothing under reduced motion.
- moves
- translate ±0.25rem, three times
- default
- slow total, ease-in-out
--duration-slow: 320ms; --motion-shake: 0.25rem;
Three oscillations whatever the duration; past about 0.5rem it stops being a refusal and becomes a tantrum.
tick
A number changing. Tabular figures so the line does not reflow as the digits do.
- moves
- the value, counted; the digits, not the layout
- default
- base, ease-out
31skills
--duration-base: 200ms; --ease-out: var(--ease-out);
A spring here counts past the number and back, which is honest about the physics and wrong for a total.