Chapter 1

Easing, Timing & Spacing

Curves that map progress to motion.

cubic-bezier()linear()CSS keywords
Duration-based easing reshapes how a value travels from A to B. cubic-bezier() gives four control points; linear() (2023) approximates springs and bounces with many points.

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).

Live cubic-bezier editor live demo
cubic-bezier(0.25, 0.1, 0.25, 1)
presets
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.

Spacing chart live demo
0 1

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 speed
ease cubic-bezier(0.25, 0.1, 0.25, 1) CSS default; gentle in/out
ease-in cubic-bezier(0.42, 0, 1, 1) accelerate from rest
ease-out cubic-bezier(0, 0, 0.58, 1) decelerate to rest
ease-in-out cubic-bezier(0.42, 0, 0.58, 1) slow at both ends

linear() — 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() bounce — multi-point easing live demo
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.

linear() easing function Baseline 2023
Chrome/Edge
Firefox
Safari

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.

hover / feedback ~150–300ms
most UI transitions 200–500ms
larger / full-screen ~500ms
> 500ms feels sluggish

Playground: 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.