cubic-bezier Editor
runs in your browserDrag a CSS cubic-bezier() easing curve or paste one to edit, see where it overshoots, and copy it for CSS, Tailwind, Motion, SwiftUI or Jetpack Compose.
The curve
= easeDrag a handle, or use the four fields — the arrow keys step them.
What it does
- It starts at an even pace and ends slowly.
- Halfway through the time, it is 80% of the way there.
In motion
a mark for each tenth of the timeThe same positions are in the table of progress through the time, below.
Copy it
transition-timing-function: ease;ease-[cubic-bezier(0.25,0.1,0.25,1)]easing: 'ease'ease: [0.25, 0.1, 0.25, 1].timingCurve(0.25, 0.1, 0.25, 1, duration: 0.6)CubicBezierEasing(0.25f, 0.1f, 0.25f, 1f)Progress through the time
| Time | 0% | 25% | 50% | 75% | 100% |
|---|---|---|---|---|---|
| Progress | 0.0% | 40.9% | 80.2% | 96.0% | 100.0% |
about this tool
Drag the two handles, or paste a timing function from your stylesheet, and see exactly what the curve does: where it is at each moment, whether it overshoots, how fast it leaves and arrives. Then copy it for CSS, Tailwind, Motion, SwiftUI or Jetpack Compose.
Reading the curve
Time runs along the bottom and progress up the side. Every CSS cubic Bézier starts at the bottom left — nothing has happened yet — and ends at the top right, finished. The two handles are the curve's control points: they pull it into shape without lying on it, which is why dragging one to 1.5 does not make the animation go 50% too far.
The steepness is the speed. Where the curve is steep, progress is changing fast against the clock; where it is flat, the thing is barely moving. The row of marks under the graph shows the same idea without any motion: one mark for each tenth of the duration, placed by how far along it has got. Where the marks bunch up it is moving slowly, and where they spread out it is moving fast.
The rule on x, and the freedom on y
The specification lets the y values go anywhere, and holds both x values to
between 0 and 1. The reason is that x is time. With both x values in range the
curve can only ever move forward in time, so every moment has one position and
one only. Push an x outside and the curve could double back on itself, which a
clock cannot do — so a browser rejects the whole value and drops the
declaration, rather than clamping it into range. Written as
transition-timing-function, whatever easing was already in force carries on;
inside a transition: shorthand, the whole transition is dropped with it.
y has no such limit, and that is where character comes from. The second handle above 1 makes the animation run past its end and come back, the way a drawer bounces off its stop; the first below 0 makes it pull back before it sets off, like a wind-up. (It is the handle nearest each end that decides: the first handle above 1 alone does not make an overshoot.) One caution: the overshoot is lost where the end value is already at a property's limit — opacity going to 1, a colour channel going to full — because it cannot go further. Fading from 0.2 to 0.6, or moving, scaling or resizing something, it shows.
The keywords, and a trap in Tailwind
| Name | CSS keyword | Tailwind v4 class |
|---|---|---|
| ease | 0.25, 0.1, 0.25, 1 | — |
| ease-in | 0.42, 0, 1, 1 | 0.4, 0, 1, 1 |
| ease-out | 0, 0, 0.58, 1 | 0, 0, 0.2, 1 |
| ease-in-out | 0.42, 0, 0.58, 1 | 0.4, 0, 0.2, 1 |
The CSS values are the specification's. The Tailwind ones are read from the
installed Tailwind's own theme, and they are not all the same curves.
Tailwind's ease-in is within 1% of the CSS one, but its ease-out and
ease-in-out get close to the end much sooner: Tailwind's ease-in-out is 95%
of the way there at 73% of the time, the CSS one not until 84%. A design written
against one and built with the other will feel subtly different, and nothing
will say why. Pasted as classes — with @apply, in a class attribute, beside
other utilities, or as an --ease- variable — they are read as Tailwind; a
bare ease-out on its own is read as the CSS keyword, and the page says which
it took and that the other exists.
ease, the default for every CSS transition, is lopsided on purpose: it starts
gently, speeds up early and spends the second half arriving — 80% of the way
there at half its duration, which is why so much of the web seems to arrive
softly.
The presets are approximations
The named curves from easings.net — easeOutCubic, easeInOutBack and the rest —
are cubic approximations of curves defined by other formulas: powers, sines,
exponentials, circles. The tests here check every one against the exact
formula published beside it. The single ease-in and ease-out versions stay
within about 1.2% of the true curve, the Expo pair being the worst. The ease-in-out versions of the steep ones are
the weakest, off by up to 3.9% for easeInOutCirc and 3.75% for easeInOutExpo,
because one cubic cannot match a curve built from two halves joined in the
middle. Elastic and bounce are missing altogether: a cubic's progress turns
at most twice, so it cannot swing back and forth, and in CSS those are now
written with linear(), a list of points.
What is worked out, and how
Finding where the animation is at a given moment means solving the curve's x
polynomial for that moment, then reading its y. This uses Newton's method with
bisection to fall back on — the approach of WebKit's UnitBezier and
Chromium's CubicBezier, with a tighter tolerance — and the tests check it
against a brute-force second method to a ten-thousandth on the most awkward
curves, and far closer on ordinary ones. Before and after the duration, the
curve is extended along its end tangents, as the specification says. The curve on the graph is drawn by the browser's
own SVG Bézier, so the line is the curve exactly rather than a join-the-dots
approximation, and the moving preview is animated by the browser's own CSS
cubic-bezier() — what moves here is what your page will do.
The preview plays only when you press Play, and never on a device that asks for less motion; the marks show the same thing standing still. The handles are for a pointer; the four fields beside the graph do the same job from a keyboard or a screen reader, and step with the arrow keys. Nothing is stored, and nothing you enter leaves the page.
For the rest of an element's look, the box shadow generator layers the shadows a hover might lift, and the gradient generator builds the fills.
A curve made here can replace the easing keyword in the animation line of a
loader from the CSS loader generator; keep linear for the
spinning ones, or they pulse.
questions
- What do the four numbers in cubic-bezier() mean?
- They are two points, x1, y1 and x2, y2. The curve always runs from (0, 0) to (1, 1), with time along the bottom and progress up the side, and those two points pull it into shape without lying on it. In cubic-bezier(0.25, 0.1, 0.25, 1), the keyword ease, the first point sits just under the diagonal, so it starts a little slower than a straight line; the second sits high on the left, which is what makes it speed up early and spend the second half arriving.
- Why do the x values have to be between 0 and 1?
- Because x is time, and time cannot run backwards. With both x values between 0 and 1 the curve only ever moves forward in time, so every moment has exactly one position. Outside that the specification makes the value invalid, and a browser drops the declaration rather than clamping it. Written as transition-timing-function, the easing already in force still applies; inside a transition: shorthand, the whole transition goes, duration and all.
- Can the y values go below 0 or above 1?
- Yes, and that is how an easing overshoots or winds up. The second handle above 1 makes the animation run past where it is going and come back; the first below 0 makes it pull back before it starts. The curve does not reach the handle — easeOutBack puts one at 1.56 and overshoots by about 10%. The overshoot is lost only where the end value is already at a property’s limit, such as opacity going to 1; on a transform or a size it shows.
- What is the difference between ease and ease-in-out?
- ease is cubic-bezier(0.25, 0.1, 0.25, 1): it starts gently, speeds up early, and spends the second half arriving — 80% of the way there at half its duration. ease-in-out is cubic-bezier(0.42, 0, 0.58, 1): slow at both ends and symmetrical, so it is exactly halfway at half the time. ease is the default for CSS transitions, which is why so many interfaces feel like they arrive softly.
- Why does Tailwind’s ease-in-out look different?
- Because it is a different curve with the same name. Tailwind v4 defines ease-in-out as cubic-bezier(0.4, 0, 0.2, 1), where CSS defines it as (0.42, 0, 0.58, 1): the Tailwind one is 95% of the way there at 73% of the time, the CSS one at 84%. Its ease-out differs as much; its ease-in is within 1% of the CSS keyword. Pasted as classes — with @apply, a class attribute, or other utilities — they are read as Tailwind; a bare name is read as CSS, and the page says which.
- Are the easings.net curves exact?
- No — they are cubic approximations of curves defined by other formulas, and the tool’s tests measure how close each one is. The single ease-in and ease-out versions stay within about 1.2% of the true curve, the Expo pair being the worst. The ease-in-out versions of the steep ones are the weakest: easeInOutCirc is off by up to 3.9% and easeInOutExpo by 3.75%, because one cubic cannot match a curve built from two halves. Elastic and bounce are not there at all: a cubic turns at most twice, and cannot swing back and forth.
- How do I use the curve outside CSS?
- The Copy panel writes it for each platform. Motion and Framer Motion take the four numbers as an ease array; the Web Animations API takes the CSS value as its easing; SwiftUI takes them in .timingCurve with a duration; Jetpack Compose takes them in CubicBezierEasing. Tailwind gets one of its own class names where the values match, or an arbitrary ease-[cubic-bezier(…)] class written without spaces, which would otherwise split the class in two.
- Does the preview move the way my site will?
- It is the same code: the moving square is animated by the browser with the CSS cubic-bezier() shown, not by a copy of it. The marks above it are worked out here — one for each tenth of the duration — and agree with a brute-force check to a ten-thousandth even on the most awkward curves. On a device that asks for less motion the preview does not play, and the marks show the same thing standing still.
- Can it edit steps() or linear() easings?
- No. steps() jumps between positions rather than curving, and the newer linear() easing is a list of points joined by straight lines, which is how bounces and springs are written in CSS now. Neither is a cubic Bézier, so pasting one says what it is rather than guessing.