Easing, Timing & Spacing
Curves that map progress to motion.
Duration tells you how long a value takes to travel from A to B. Easing tells you how it spends that time — where it lingers, where it sprints. Same distance, same duration, completely different feel. This is the single highest-leverage knob in interface motion.
Easing reshapes the journey, not the destination
Picture a value animating 0 → 100 over 600ms. With linear timing it
covers an equal slice of ground every frame — mechanical, lifeless. With easing, the browser
redistributes that same travel across the same time: maybe it crawls for the first half and then
snaps home, or bursts out and glides to a stop.
Animators call the visual record of this the spacing chart: mark where the object sits on equally-spaced frames. Where the marks bunch together, motion is slow; where they spread apart, it's fast. The chart below is exactly that — drag the editor further down and watch the dots crowd the ends.
cubic-bezier(x1, y1, x2, y2): four points, two you control
CSS timing functions are cubic Bézier curves on a unit square. The horizontal axis is elapsed time (0→1); the vertical axis is progress (0→1). Two
anchor points are fixed — P0 = (0, 0) and P3 = (1, 1) — and you drag the
two control points P1 and P2. Their y values may exceed [0, 1],
which is how a curve can overshoot (anticipation / bounce-back).
cubic-bezier(0.25, 0.1, 0.25, 1)cubic-bezier(0.25, 0.1, 0.25, 1) Drag the two colored handles. The probe box on the right replays with whatever curve you build — including y > 1 overshoots.
The same curve as a spacing chart
This reads the live editor above. Each dot is a frame sampled at an equal time step; its position
is the eased progress. Pick ease-in-out and you'll see the dots pile up at both ends.
13 dots sampled at equal time intervals, placed at their eased position. Bunching = slow motion · spread = fast motion.
The CSS keywords are just named beziers
The five keyword easings are nothing more than presets for the curve above. Knowing their exact control points lets you reason about them — and tweak them when “almost right” isn't right.
linear cubic-bezier(0, 0, 1, 1) constant speedease cubic-bezier(0.25, 0.1, 0.25, 1) CSS default; gentle in/outease-in cubic-bezier(0.42, 0, 1, 1) accelerate from restease-out cubic-bezier(0, 0, 0.58, 1) decelerate to restease-in-out cubic-bezier(0.42, 0, 0.58, 1) slow at both endslinear() — baking springs and bounces into CSS
A single cubic Bézier is monotonic-ish: it can overshoot once, but it can't oscillate.
You can't express a multi-bounce or a damped spring with four numbers. The linear() easing function (shipped across browsers in 2023) solves this: it takes a list of points and draws straight segments between them. With enough points you
can approximate any curve — including a spring or a bouncing ball — natively, no JS on the main
thread.
linear(0, 0.063, 0.25, 0.563, 1, 0.813, 0.938, 1, 0.984, 1) Ten points trace a bounce. Each is a progress value at an evenly-spaced moment; CSS connects them with lines. This is how you “bake” a spring (Chapter 4) into static CSS.
Widely available since 2023. cubic-bezier() and the keyword easings have universal support.
Timing: how long should it take?
Duration is the other half of the equation. Too fast and the motion is invisible — the user can't track the relationship between before and after. Too slow and it feels laggy, making the whole UI seem unresponsive.
~150–300ms200–500ms~500msfeels sluggishPlayground: edit the timing function
The box below is yours. Swap the transition-timing-function — try cubic-bezier(0.34, 1.56, 0.64, 1) for an overshoot, or paste a linear(...) bounce. Change the duration and feel the sweet spot.