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.
- % of the time, % of the way
- 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
.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
- Set the two control points. The curve, the CSS and the replay all update together.
- Press “Play it” to watch the browser run the timing function you are about to copy, rather than a JavaScript imitation of it.
- 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?
Which curve should I use for a UI?
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?
opacity, or the duration is too short for the
overshoot to be visible. Try the same curve on transform at 400ms.