Writing · Motion

A guide to easing and springs

How a timing curve turns time into progress: the named curves, cubic-bezier() you can drag, springs and their physics, what happens when a move is interrupted, and which one to reach for. 11 minutes, 6 demos.

Try it: one move, six curves

ease-out: fast away, slow into place: for things arriving

box.csscss
1.box {
2 transition: transform 1000ms ease-out;
3}
Curve
Animate
On this page

01 · The idea

Time in, progress out

Every animation answers two questions: how long it takes, and how it spends that time. The first is a duration. The second is a timing function, and it’s the one that decides how a move feels.

A timing function is a curve that takes time and gives back progress. Time runs from 0 to 1 across the bottom, progress from 0 to 1 up the side, and at each moment the curve says how far along the move should be. Where it’s steep, the thing is moving fast; where it’s flat, it’s barely moving. Read the curve’s slope and you’ve read the motion. The demo at the top plays the same move on six of them, the dot on the curve and the box in step.

02 · The named curves

Five curves you already have

CSS names five, and between them they cover most of what an interface does:

  • linear: the same speed from start to end. Nothing in the world moves like that, so it reads as mechanical, which is exactly right for a progress bar or a spinner.
  • ease: the browser’s default when you don’t name one. A quick start and a long, soft landing.
  • ease-in: starts slow, ends fast. Something leaving, speeding up as it goes.
  • ease-out: starts fast, ends slow. Something arriving: it answers at once, then settles into place.
  • ease-in-out: slow, fast, slow. Something travelling from one place on the screen to another.

The sixth in the demo isn’t a curve at all. steps(4) jumps four times and holds between them, with no motion in between. It’s for things that should tick rather than glide. This site’s film grain is one: its texture jumps to a new spot ten times a second on steps(1), because grain that slid would look like a smear, not film.

03 · cubic-bezier()

Drawing your own curve

The named curves are all one shape underneath: a cubic Bézier, a curve from the bottom-left corner to the top-right, pulled by two handles. ease-out is cubic-bezier(0, 0, 0.58, 1). Write your own four numbers and you get any curve of that family. Drag the handles:

cubic-bezier(): draw your own curve
box.csscss
1.box {
2 transition: transform 1s cubic-bezier(0.22, 1, 0.36, 1);
3}
Start from

The handles’ first numbers are time, so they have to stay between 0 and 1: a move can’t go back in time. The second numbers are progress, and they’re free. Pull one above 1 and the move overshoots its target before coming back; pull one below 0 and it pulls back before it sets off. That’s how a curve fakes a little bounce.

A custom curve is how a design gets its own feel. This site’s arrivals use cubic-bezier(0.22, 1, 0.36, 1), an ease-out that leaves faster and lands softer than the browser’s; the case study’s detail on motion has the rest of its set.

motion.css
:root {
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-move: cubic-bezier(0.65, 0, 0.35, 1);
}

.menu { transition: opacity 200ms var(--ease-out); }

04 · Springs

Motion with weight instead of a clock

A curve has a duration: it’s told how long to take. A spring isn’t. It’s a simulation of a weight on a spring, pulled toward its target, and it takes as long as the physics takes. Three numbers describe it:

  • Stiffness: how hard the spring pulls. Stiffer is quicker and snappier.
  • Damping: how much it resists, like moving through air or honey. Low damping lets it swing past the target and back; high damping lets it creep in without overshooting.
  • Mass: how heavy the thing is. Heavier is slower to start and slower to stop.
A spring: no duration, only physics

settles in 1.06s · overshoots 20%

Box.jsxjsx
1<motion.div
2 animate={{ x: 240 }}
3 transition={{ type: "spring",
4 stiffness: 170,
5 damping: 12,
6 mass: 1,
7 }}
8/>
Set by

Those three are hard to reason about: change one and the time and the bounce both move. So Motion (the animation library this site uses) also takes two friendlier ones. bounce is how much it overshoots, from 0 (none) to 1 (it wobbles for seconds); below 0, down to -1, it sets off quicker and creeps the last bit, still arriving on time. visualDuration is how long it takes to look arrived, with any bounce after. Switch the demo to them: it shows the stiffness and damping they turn into, and marks the visualDuration on the curve, where the move has mostly landed and the wobble begins.

Motion leans on springs without being asked: move, turn or scale something and it gets a spring unless you name a curve. A fade or a colour change gets a 0.3s curve instead, since there’s nothing there to bounce.

05 · Interrupted

What a curve can't do

Here’s where springs earn their place. A curve is a plan made at the start: from here to there, in this long, on this shape. If something changes halfway (the visitor clicks again, a menu is closed while it’s still opening), the plan has to be thrown away and a new one started from where the thing is now. And a new curve knows nothing of the speed the thing had: an ease-in-out sets off from rest, an ease-out at full speed the other way. Either way the speed jumps.

Interrupted: sent back mid-move

← the last 2.6 seconds, position up · dashed: sent back

ease-in-out, 600ms: restarts from rest

spring (stiffness 180, damping 20): keeps its speed

Look at the trace. Where the box is sent back, the eased line turns a sharp corner: it was moving at full speed, and for a moment it isn’t moving at all. The browser does shorten a CSS transition that’s reversed, but it still starts the curve over. The spring’s line just bends: it keeps the speed it had, slows, turns and comes back, the way something with weight would.

So springs are the right tool for anything a hand can interrupt: toggles hit again and again, things dragged and let go with speed, a card flicked away. One catch, in Motion’s docs: only a spring set by stiffness, damping and mass carries its speed into the next move. One set by visualDuration and bounce starts each move from rest, by design (its source says carried speed made small moves swing wildly). For a drag or a flick, use the physical numbers.

06 · Springs in CSS

A spring, written as CSS

A spring isn’t only a JavaScript thing any more. CSS’s linear() takes a list of points and joins them with straight lines, and with enough points it can trace any curve, a spring’s included. Motion will write one for you: a spring turned into a string is its duration and its linear().

A spring, written as CSS

Motion: { type: "spring", visualDuration: 0.5, bounce: 0.3 }

CSS: a transition on linear()

box.csscss
1.box {
2 transition: transform 850ms linear(
3 0, 0.0465, 0.158, 0.3009, 0.4514,
4 0.5935, 0.7182, 0.821, 0.9013, 0.9605,
5 1.0011, 1.0267, 1.0405, 1.0457, 1.045,
6 1.0407, 1.0345, 1.0276, 1.0209, 1.0148,
7 1.0097, 1.0057, 1.0027, 1.0005, 0.9991,
8 0.9983, 1, 1
9 );
10}

The two boxes land together because they’re the same curve, one simulated and one written down. Every current browser reads linear() (since 2023). What CSS can’t do is the last section: its spring is a fixed plan with a fixed duration, so it still starts over from rest when it’s interrupted. Use it for a spring’s shape on things that play once, like an element appearing, and keep real springs for what a hand drives.

spring-to-css.js
import { spring } from "motion";

String(spring(0.5, 0.3)); // visualDuration, bounce
// "850ms linear(0, 0.0465, 0.158, …, 1)"

The 850ms is longer than the 0.5 you asked for, because it includes the settle after the move has visibly landed. Time anything that plays alongside it, a fade say, off the 0.5.

07 · Which one, when

Choosing by what the thing is doing

Pick by what the element is doing, not by what looks nice in isolation. Each case below plays the usual choice next to a common mistake:

Which one when: right and wrong, side by side

✓ right: ease-out in, ease-in out, 200ms

✗ wrong: ease-in both ways, 400ms

Something arriving should start fast and settle, so it answers the click at once. Eased in, the menu hangs still for its first frames, and the click feels missed.

Case
  • Arriving: ease-out. Fast away, soft landing: it answers at once.
  • Leaving: ease-in, and quicker than it arrived. It’s going; let it go.
  • Moving across the screen: ease-in-out. Gentle at both ends, like an object.
  • Progress, loops, anything tied to scroll: linear. The rate is the information.
  • Anything a hand can interrupt: a spring, set by stiffness and damping if it carries speed.
  • How long: about 150–300ms for small things, up to ~500ms for something large crossing the screen. A press: 150ms.
  • How much bounce: none where a value must be read (a dashboard, a price), a little where the interface is meant to feel alive, more only for a flourish meant to be noticed.
  • For anyone who asks for less (prefers-reduced-motion): fade rather than move. This site’s detail on switching motion off shows how.