Cubic-bezier generator — drag the curve, watch it move, read the numbers
Drag two control points to shape an easing curve and copy the cubic-bezier() value it produces. A bar replays the curve beside a second bar running linear, so you are comparing your easing against the thing it is meant to improve on rather than against a memory. Under it the page prints how much of the distance has been covered at each quarter of the duration, and how far past 100% the curve travels if it overshoots — the two readings that turn “that feels wrong” into a number you can change.
- Nothing is uploaded or saved
- Works offline
Left to right is time. Bottom to top is progress. Drag either handle, or focus one and use the arrow keys — hold shift for larger steps.
The two x sliders stop at 0 and 1 because a curve that moves backwards in time is not a function of time, and CSS rejects it outright. The y sliders do not stop there, and that is where overshoot lives.
Both bars start together and finish together — every difference between them is the curve, not the duration. The pause at the end is added by this page so the loop is readable; it is not part of the easing.
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
This curve never passes 100%, so nothing overshoots.
| Time | Distance covered |
|---|---|
| 10% | 9.5% |
| 25% | 40.9% |
| 50% | 80.2% |
| 75% | 96% |
| 90% | 99.4% |
Read the middle row against the bars above: at half the duration a linear animation is at 50%, and this one is at 80.2%.
These four are the exact curves the keywords stand for, not approximations of them.
linear is deliberately not in that list. In current CSS it is its own easing function rather than a Bézier keyword, though the graph it draws is the same one you get from cubic-bezier(0, 0, 1, 1) — the dashed diagonal above.
Our own numbers, shown to demonstrate a behaviour. None of them is a standard, and none is presented as one.
Related tools on this site: lay out the thing that is moving, raise it off the page while it moves and fill it with a gradient.
How it works
- 1
Read the graph before you drag anything
Left to right is time, from the moment the animation starts to the moment it ends. Bottom to top is progress, from nothing moved to fully arrived. The dashed diagonal is a constant speed. Any curve above that diagonal is ahead of schedule at that instant, and any curve below it is behind — which is the whole vocabulary you need to read every easing curve you will ever see.
- 2
Move the handles
The green handle steers the start and the amber one steers the end. Pulling the green handle up and right makes the animation leave quickly; dragging the amber one down and left makes it arrive slowly. Each handle can be focused with the keyboard and nudged with the arrow keys, in steps of 0.01 or 0.05 with shift held, when a value needs to land exactly.
- 3
Push a handle past the top or the bottom
The vertical axis is not limited to the 0–1 box. Take the end handle above the top edge and progress passes 100% and comes back — an overshoot, the small bounce that makes a panel feel like it has weight. Take the start handle below the bottom and the element retreats before it sets off, which reads as a wind-up. The graph expands to keep both handles in view, and the reading beside the CSS tells you the exact peak.
- 4
Watch it against linear
The two bars start together and finish together, so every difference you can see between them belongs to the curve and not to the duration. Set the duration to something realistic before judging anything: a curve tuned at three seconds nearly always feels sluggish at the 200ms a real interface uses, because the slow part of it is where all the time went.
- 5
Copy the value
The output is the timing function on its own, ready to drop into a transition or an animation. It is the same string the preview above is running — the playback uses the browser’s own animation engine with your cubic-bezier() as its easing, so what you watched is what you are pasting.
Frequently asked questions
What do the two control points actually mean?
They set the direction and strength of the curve at each end. A cubic Bézier has four points; in CSS the first and last are fixed at (0,0) and (1,1) — nothing has moved when the animation starts, everything has moved when it ends — and you supply the two in between as the four numbers x1, y1, x2, y2. The curve leaves the origin heading straight at the first control point and arrives at the finish coming straight from the second, and how far away each handle sits decides how long the curve keeps going in that direction. That is why dragging the first handle further right, without moving it up, produces a longer stall at the start: the curve is committed to that flat direction for longer.
Why are the x values locked to 0 and 1 when the y values are not?
Because x is time. If a control point’s x could go past 1 or below 0, the curve would bend back on itself, and there would be points in the animation with two different amounts of progress at the same instant — which is not something an element can do. CSS refuses those values outright, and a declaration containing one is dropped. y is a different quantity: it is distance covered, and there is no rule saying an element may not travel past its destination and come back, or set off backwards first. That asymmetry is not an oversight in the spec, and it is not an oversight in this tool either — it is where overshoot and anticipation come from.
Why does ease feel different from ease-in-out?
Because ease is not symmetrical and ease-in-out is. ease — which is what a transition uses if you never name a timing function — is cubic-bezier(0.25, 0.1, 0.25, 1): it climbs steeply early on and spends the back half of the duration settling. ease-in-out is cubic-bezier(0.42, 0, 0.58, 1), with control points mirrored around the centre, so it accelerates and decelerates by exactly the same amount. Load both from the presets above and read the halfway row of the table: they cross the same point at the middle of the duration and get there along noticeably different paths. ease is the livelier of the two; ease-in-out is the more mechanical.
Which curve should I use for something appearing on screen?
Something like ease-out — cubic-bezier(0, 0, 0.58, 1) — or a stronger version of it. An element that enters should arrive at rest, which means it needs to be decelerating at the end, and ease-out is the shape that does that. The reverse holds for exits: an element leaving does not need to be watched arriving, so ease-in, cubic-bezier(0.42, 0, 1, 1), lets it accelerate away and get out of the reader’s way. Using the same symmetrical curve for both is the most common reason a set of transitions feels correct in isolation and heavy in use.
Does the easing change how long the animation takes?
No. The duration is fixed by transition-duration or animation-duration, and the timing function only decides how the distance is spread across it. Two elements with the same duration and different easings start together and stop together — the bars above are set up to demonstrate exactly that. What easing changes is the *apparent* speed: a curve that covers 80% of the distance in the first third of its time feels fast even though it has not finished any sooner, because the part the eye tracks is over early.
My overshoot is being cut off. Where did it go?
Almost certainly clipped by a parent with overflow hidden. An overshooting curve moves the element past its final position and back, so for a few frames it is further along than the layout expects — and if an ancestor is clipping, those frames are the ones that disappear. The other regular cause is overshooting a property that cannot exceed its own bounds in a visible way: opacity above 1 and below 0 is clamped, so an overshoot on a fade does nothing at all. Overshoot belongs on transform, where there is room for it.
Does cubic-bezier work on every property?
It works wherever a value is being interpolated, which excludes properties that jump rather than blend. `display` and `visibility` change in a single step; a background-image swapped for a different gradient does not blend either. For everything that does interpolate — transform, opacity, colour, lengths — the timing function applies to the interpolation and works the same way. Note that easing is applied per property, so two properties on the same element can carry different curves, and in a keyframe animation the timing function applies between each pair of keyframes rather than across the whole sequence.
What about steps() and linear()?
They are the other two shapes of timing function, and neither is a Bézier. steps() splits the duration into a fixed number of jumps, which is how a sprite-sheet animation or a deliberately mechanical counter is built — there is no in-between state at all, so no curve could express it. linear() is newer: it takes a list of progress values and joins them with straight segments, which lets it approximate a spring or a bounce with several direction changes, something a single cubic Bézier with two control points cannot do. Check its support against your own browser targets before shipping it — this page will not make that claim on your behalf.
Should the same curve go on transition-timing-function and animation-timing-function?
The value is interchangeable, but where it applies is not. On a transition the curve spans the whole change, start to finish. On a keyframe animation it spans each pair of adjacent keyframes, so an animation with four keyframes and one timing function actually runs that curve three times — accelerating and decelerating at every stop along the way, which is rarely what anyone means. If you want a single curve across a multi-keyframe animation, either reduce it to two keyframes or set a per-keyframe timing function of linear on the intermediate ones so they do not each ease again.