CSS Cubic Bezier Easing Generator

Build an easing curve, watch the browser run it, and read what share of the distance is covered at each share of the time. Overshoot is drawn, not clipped.

Enable JavaScript to change the curve; the CSS below is for the defaults.

Time cannot run backwards, so this is clamped to 0 and 1. CSS throws the whole declaration away if it is not.

Above 1 overshoots the target and comes back. Below 0 backs up before setting off.

Duration changes how a motion feels more than the curve does. 150 to 300ms for most interface movement.

transform and opacity are the two a browser can animate without recalculating layout.

  • 10% of the time, 40.4% of the way
  • 25% of the time, 81.6% of the way
  • 50% of the time, 108.7% of the way
  • 75% of the time, 106% of the way
  • 90% of the time, 101.3% of the way
easing.css
.toast {
	transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* The curve leaves the 0 to 1 range on the progress axis, so the
   animation overshoots and settles. That is legal and it is the only
   way one timing function can do it. It cannot animate a property
   whose values are clamped, such as opacity. */

An easing curve maps time to progress. Both axes run 0 to 1, the ends are pinned at (0,0) and (1,1), and the two control points are the part you choose. Everything else follows from that.

Time cannot run backwards, so the X coordinates are clamped to 0 and 1. If you write one outside that range, CSS throws the whole declaration away and the animation silently falls back to ease.

Progress has no such limit, and that is where the interesting curves live. A Y above 1 overshoots the target and settles back, which is what makes a motion feel physical. A Y below 0 backs up before setting off. The plot here draws the curve leaving its box rather than clipping it, because a clipped overshoot looks like a bug in the tool rather than the effect you asked for.

How to use

  1. Set the two control points. The curve, the CSS and the replay all update together.
  2. Press “Play it” to watch the browser run the timing function you are about to copy, rather than a JavaScript imitation of it.
  3. Read the samples under the plot: they say what share of the distance is covered at each share of the time, which is the thing a curve makes hard to see.

Example

The standard back-out, which overshoots and settles:

.toast {
	transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* The curve leaves the 0 to 1 range on the progress axis, so the
   animation overshoots and settles. That is legal and it is the only
   way one timing function can do it. It cannot animate a property
   whose values are clamped, such as opacity. */

At a quarter of the time it is already 82 percent of the way there, it passes the target around halfway, peaks near 109 percent, and comes back. That is the whole personality of the movement, and it is four numbers.

When the control points match a keyword exactly, the output uses the keyword: ease-in-out rather than cubic-bezier(0.42, 0, 0.58, 1), because the shorter one is easier to read in a diff.

Pitfalls

An invalid X kills the declaration. Outside 0 to 1 the whole transition is dropped, and the browser uses ease. The tool clamps for you, which is why the sliders stop.

Overshoot cannot animate a clamped property. opacity above 1 is the same as 1, so a bouncing curve on opacity looks like it pauses. Use it on transform.

ease is not symmetric. The CSS default is cubic-bezier(0.25, 0.1, 0.25, 1), which starts gently and finishes very gently. It is not the same as ease-in-out, and it is the reason default transitions often feel slightly slow at the end.

linear reads as mechanical for movement and correct for colour. A fade or a colour change at a constant rate looks fine; an object crossing the screen at a constant rate looks like a machine.

Duration matters more than the curve. Most interface movement wants 150 to 300ms. A perfect curve at 600ms still feels sluggish, and no curve rescues an animation that is too long.

One cubic bezier cannot bounce twice. It has one hump. Multiple bounces need keyframes, or the newer linear() function, which approximates a spring with a list of points.

Respect prefers-reduced-motion. Overshoot and large movement are exactly what that setting exists to turn off. Wrap the transition in the media query rather than shipping it unconditionally.

Compatibility

cubic-bezier() and the keywords work in every browser in use.

The curve is solved by bisection rather than Newton’s method: it cannot diverge on a curve with a nearly flat section, which is exactly what an aggressive ease-in is. Sixty iterations puts the answer well below a pixel of error at any realistic size.

The whole calculation is mirrored in PHP for the server-rendered first paint, and a case file compares the two on every build, including a curve with control points outside the legal range and a case with empty fields. That comparison is what caught an empty field being read as zero in one language and as the default in the other.

The plot is an SVG with a viewBox taller than the unit square, so an overshooting curve is drawn outside the dashed box instead of being clipped. The replay uses transform: scaleX with the generated timing function, so what you watch is the browser running the copied CSS.

Frequently asked questions

What do the four numbers mean?
The X and Y of the first control point, then the X and Y of the second. X is time, Y is progress, and the start and end points are fixed.
Which curve should I use for a UI?
Something that starts fast and ends slow for things entering the screen, and the reverse for things leaving. ease-out is the safe default for entrances; a back-out adds personality at the cost of attention.
Why does my bouncing animation look flat?
Either it is animating a clamped property such as opacity, or the duration is too short for the overshoot to be visible. Try the same curve on transform at 400ms.
What is linear()?
A newer timing function that takes a list of progress points, which lets it approximate a spring or several bounces. It is supported in current browsers and it is the right tool when one bezier is not enough.
Should I animate with CSS or JavaScript?
CSS for state changes that have a start and an end, which is nearly all interface movement. JavaScript when the animation has to follow a gesture or be interrupted mid-flight with a new target.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.