Why You Are Here
Animation timing is the difference between an interface that feels considered and one that feels cheap. The duration matters, but the easing curve, how the motion accelerates and decelerates, is what people actually perceive as quality, even though almost nobody could name it.
The problem is that cubic-bezier(0.34, 1.56, 0.64, 1) tells you nothing by looking at it. Four decimals do not communicate motion. You have to see the curve and watch it play.
The first situation is that the built-in keywords are not enough. CSS gives you ease, ease-in, ease-out, ease-in-out, and linear. They are fine, but they are also the defaults everyone else uses, and ease in particular has a slightly dated, sluggish feel. A custom curve is how an interface develops a motion character of its own.
The second is design handoff. A motion designer specifies easing in After Effects or Figma, and you need the equivalent cubic-bezier() values to reproduce that feel in CSS rather than approximating by eye.
The third is simply learning. Dragging the handles and watching the animation respond teaches you how bezier curves work far faster than reading about control points.
What This Tool Does
The Cubic Bezier Generator gives you a visual curve editor with two draggable control points and a live animation preview.
Drag the handles to reshape the curve, or type exact coordinate values if you are matching a specification. The preview animation replays as you adjust, so you can feel the difference rather than guessing from numbers. Start from a preset if you want a known-good curve as a base.
When the motion feels right, copy the cubic-bezier() value into your stylesheet.
How Cubic Bezier Easing Works
A cubic bezier curve maps TIME (horizontal axis) to PROGRESS (vertical axis). The animation always starts at (0,0) and ends at (1,1). Those two points are fixed. What you control is the two points in between.
transition-timing-function: cubic-bezier(x1, y1, x2, y2); /* x1, y1 = first control point, governs how the animation begins x2, y2 = second control point, governs how it ends */
Reading the curve is straightforward once you know what to look for. Where the curve is STEEP, progress is changing quickly, so the animation is moving fast. Where it is SHALLOW, the animation is slow. A curve that starts shallow and ends steep accelerates. One that starts steep and ends shallow decelerates.
The rule that catches everyone
X represents time, and time cannot run backwards or extend past the animation's duration. If you enter an X value below 0 or above 1, the value is invalid and the browser discards the entire declaration. Your element will simply not animate, with no error message.
Y represents progress, and progress is allowed to exceed its endpoints. A Y value above 1 means the animation overshoots past its target and settles back. A Y value below 0 means it pulls backwards before moving forward. This is where snappy, spring-like motion comes from.
CSS Keyword Equivalents
Every built-in easing keyword is a cubic bezier underneath. These are exact.
| Keyword |
Equivalent cubic-bezier() |
Character |
|
linear |
cubic-bezier(0, 0, 1, 1) |
Constant speed, mechanical |
|
ease |
cubic-bezier(0.25, 0.1, 0.25, 1) |
CSS default, gentle both ends |
|
ease-in |
cubic-bezier(0.42, 0, 1, 1) |
Starts slow, ends fast |
|
ease-out |
cubic-bezier(0, 0, 0.58, 1) |
Starts fast, ends slow |
|
ease-in-out |
cubic-bezier(0.42, 0, 0.58, 1) |
Slow at both ends |
Preset Curve Reference
Approximate cubic-bezier() equivalents for the standard easing families. These are the values used across most animation libraries.
| Easing |
cubic-bezier() Value |
|
easeInSine |
0.12, 0, 0.39, 0 |
|
easeOutSine |
0.61, 1, 0.88, 1 |
|
easeInOutSine |
0.37, 0, 0.63, 1 |
|
easeInQuad |
0.11, 0, 0.5, 0 |
|
easeOutQuad |
0.5, 1, 0.89, 1 |
|
easeInOutQuad |
0.45, 0, 0.55, 1 |
|
easeInCubic |
0.32, 0, 0.67, 0 |
|
easeOutCubic |
0.33, 1, 0.68, 1 |
|
easeInOutCubic |
0.65, 0, 0.35, 1 |
|
easeInQuart |
0.5, 0, 0.75, 0 |
|
easeOutQuart |
0.25, 1, 0.5, 1 |
|
easeInOutQuart |
0.76, 0, 0.24, 1 |
|
easeInExpo |
0.7, 0, 0.84, 0 |
|
easeOutExpo |
0.16, 1, 0.3, 1 |
|
easeInOutExpo |
0.87, 0, 0.13, 1 |
|
easeInCirc |
0.55, 0, 1, 0.45 |
|
easeOutCirc |
0, 0.55, 0.45, 1 |
|
easeInBack (undershoot) |
0.36, 0, 0.66, -0.56 |
|
easeOutBack (overshoot) |
0.34, 1.56, 0.64, 1 |
|
easeInOutBack |
0.68, -0.6, 0.32, 1.6 |
Material Design curves
| Purpose | Value |
|
Standard |
0.4, 0.0, 0.2, 1 |
|
Decelerate (entering) |
0.0, 0.0, 0.2, 1 |
|
Accelerate (exiting) |
0.4, 0.0, 1, 1 |
Choosing Easing and Duration Together
A well-shaped curve at the wrong duration still feels wrong. These two decisions are inseparable, and the pairing below covers most interface work.
| Element | Duration |
Suggested Easing |
Reasoning |
|
Button hover |
150ms |
ease-out |
Must feel immediate |
|
Button press |
100ms |
ease-out |
Instant feedback |
|
Tooltip appear |
150ms |
ease-out |
Fast, unobtrusive |
|
Dropdown open |
200ms |
ease-out |
Quick, decisive |
|
Dropdown close |
150ms |
ease-in |
Exits faster than entries |
|
Modal open |
250ms |
0.0, 0.0, 0.2, 1 |
Decelerates into place |
|
Modal close |
200ms |
0.4, 0.0, 1, 1 |
Accelerates away |
|
Accordion expand |
250ms |
ease-in-out |
Smooth both directions |
|
Page transition |
300ms |
0.4, 0.0, 0.2, 1 |
Larger movement, more time |
|
Toast slide in |
300ms |
0.34, 1.56, 0.64, 1 |
Slight overshoot draws the eye |
|
Loading spinner |
800ms+ |
linear |
Rotation must not pulse |
When something appears, the user needs a moment to register it. When it leaves, they have already moved on, and a slow exit feels like the interface is holding them up. Shortening the exit duration by a third is a reliable improvement.
ease-out is right for entering because it decelerates into position. Using that same curve for exiting means the element speeds up and then crawls out of view, which feels wrong. Use ease-in for exits so the element accelerates away cleanly.
What Cubic Bezier Cannot Do
This is worth being precise about, because several easing tools list bounce and elastic presets that do not do what their names suggest.
Option one: keyframes
@keyframes bounce {
0% { transform: translateY(-100px); }
50% { transform: translateY(0); }
70% { transform: translateY(-20px); }
85% { transform: translateY(0); }
92% { transform: translateY(-6px); }
100% { transform: translateY(0); }
}
Option two: the linear() function
This is the modern approach. It accepts an arbitrary list of progress stops, so it can describe oscillation that cubic-bezier() cannot.
transition-timing-function: linear( 0, 0.25, 0.6, 0.9, 1, 0.95, 1 ); /* linear() is supported in current versions of Chrome, Firefox, Safari, and Edge. If you support older browsers, declare a cubic-bezier() fallback before it. */
Accessibility — Respecting Reduced Motion
Almost no easing tool mentions this, and it matters more than the curve you choose.
Some people experience genuine nausea, dizziness, or migraine from interface motion, particularly large movement, parallax, and zoom effects. Operating systems expose a reduce motion setting for exactly this reason, and CSS can read it.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Common Mistakes to Avoid
Overshoot comes from the Y values, not the X values. If you set an X below 0 or above 1 trying to create a spring effect, the cubic-bezier() value becomes invalid and the browser silently discards the whole declaration. Your element will not animate at all, and there will be no console error explaining why. If a transition suddenly stops working after you adjust a curve, check the X values first.
The easing curve is irrelevant if the animation is dropping frames. Animating width, height, top, left, margin, or padding forces the browser to recalculate layout on every frame, which is expensive. Animating transform and opacity does not, because those can be handled by the compositor. Use transform: translate() instead of left, and transform: scale() instead of width, wherever the effect allows.
There is a temptation to slow an animation down so the carefully designed easing is visible. Resist it. Most interface transitions should sit between 150ms and 400ms. Beyond roughly half a second, an interaction starts to feel like it is lagging rather than animating, no matter how good the curve is. The easing determines how the motion feels; the duration determines whether the user experiences it as responsive or slow.
Quick Reference
| Item | Value |
|
X range |
0 to 1 (required) |
|
Y range |
Any value |
|
Y above 1 |
Overshoot |
|
Y below 0 |
Undershoot |
|
ease |
0.25, 0.1, 0.25, 1 |
|
ease-out |
0, 0, 0.58, 1 |
|
Entry |
Decelerate |
|
Exit |
Accelerate, and shorter |
|
Typical duration |
150 to 400ms |
|
Animate |
transform, opacity |
|
True bounce |
Use linear() or keyframes |
|
Always ship |
prefers-reduced-motion |
