A CSS cubic-bezier() easing function is defined by exactly four numbers — x1, y1, x2, and y2 — and the details that decide whether the curve is valid, legible, and behaves the way you expect are the meaning of each coordinate, the range that CSS permits for each one, and the way the solver samples the resulting parametric curve. The first and third numbers describe input time and must stay in the closed interval from 0 to 1. The second and fourth numbers describe output progress and may legally leave that interval, which is how anticipation and overshoot appear in a CSS transition. Because CSS animates elapsed time on the x axis, the value the user sees at 50 percent of the duration is not the curve value at t equals 0.5 in the parametric sense, it is the y value at the t whose x coordinate equals 0.5. The detail that ties all of this together is the solver, a mix of bounded Newton iterations finishing in bisection, which the CSS Cubic Bezier Generator performs locally so the sample table and Run preview stay consistent with the four coordinates you typed.

The Four Numbers That Define Every Curve
A cubic-bezier easing in CSS is a single parametric curve that always starts at the point (0, 0) and always ends at the point (1, 1). Those two endpoints are fixed by the specification and are not editable, because every transition has to start at zero progress and finish at full progress. What you control are the two intermediate control points: (x1, y1) and (x2, y2). Pull on either of them and the path the animated value follows between zero and one bends with it, which is exactly why a single cubic-bezier() declaration can produce ease, snap, drift, anticipation, or overshoot from the same four numbers.
The CSS syntax reflects that structure directly. A valid declaration looks like cubic-bezier(0.25, 0.1, 0.25, 1), with all four coordinates listed in order between parentheses. Anything missing, anything outside the parentheses, or any non-numeric token is a syntax error rather than a stylistic choice, and a browser will silently fall back to its default timing. The CSS Cubic Bezier Generator writes the complete declaration including its trailing semicolon so what you copy is what the parser expects to see.
What x and y Represent on a Cubic Bezier Graph
The graph plots two different quantities on its two axes, and confusing them is the single most common source of frustration with custom easing. The horizontal x axis is elapsed time, normalized so that the start of the transition sits at 0 and the end of the transition sits at 1. The vertical y axis is animation progress, again normalized from 0 (no movement yet) to 1 (final position). Reading a point on the curve as "at this fraction of the duration, the value has progressed by that fraction" is correct; reading it as "this is the curve's local t parameter" is the trap.
The curve itself is a parametric function in some local t that runs from 0 to 1 independently of time. CSS does not animate t; it animates the y value that corresponds to the current x value. When x1 and x2 are far apart, the parametric t that produces x equals 0.5 can sit at a completely different local position, so the visible value at the halfway mark of your duration can be much higher or much lower than 0.5. That is why easing always feels uneven in the middle for sharp curves even though the time is exactly halfway.
Valid Ranges and Why Overshoot Still Counts
W3C CSS Easing Functions Level 1 defines a cubic-bezier value as invalid when either of the x control coordinates lies outside the closed interval from 0 to 1. Browsers discard invalid timings and fall back to a default curve, which means a typo like x1 = 1.05 silently turns your custom easing into something else. The CSS Easing Functions Level 1 specification is the authoritative source for this rule, and the generator enforces it in the input layer so invalid x values cannot reach the serialized output.
The y coordinates are not bounded to the same range. y1 and y2 may be negative, may be larger than one, and may be any finite number that the interface accepts, which the generator caps at minus ten through ten. A negative y produces anticipation: the animated value moves backward at the start before moving forward. A y greater than one produces overshoot: the animated value passes the final position and settles back. Both behaviors are valid CSS and both are useful for things like button presses or spring-like entrances, but they should be inspected visually before being shipped because they can also push elements outside their containers or clip content that the design relied on staying clipped. The full treatment of this edge case is covered in Can y1 or y2 Leave the 0–1 Range in CSS cubic-bezier()?.
How to Generate and Verify a Curve in Three Steps
- Pick a starting point. Choose one of the five standard easing keywords if you want a known-good baseline, or enter four explicit control coordinates (x1, y1, x2, y2) if you are designing a custom curve from scratch. The x inputs are constrained to the CSS-permitted zero-to-one range as you type; the y inputs accept finite values between minus ten and ten so anticipation and overshoot remain reachable.
- Inspect the curve and the samples. Watch the square chart for the shape, read the five input-to-output samples at 0, 0.25, 0.5, 0.75, and 1 for the exact numeric progress at those times, and toggle the Run preview to watch a circle cross the preview area over a 900 millisecond transform transition. The preview uses the same easing string that the generator outputs, so timing you see there is the timing the browser will use.
- Copy and test in the real component. Use the Copy CSS control to write the complete transition-timing-function declaration, with its semicolon, to the clipboard. Drop it into the actual property, distance, and duration you intend to animate, and confirm the motion reads correctly under reduced-motion and focus conditions before considering the curve done.
Reading the Sample Table and Run Preview Correctly
The sample table answers a question the control-point diagram alone cannot: how far has the animated value progressed when a given fraction of time has passed. For each input at 0, 0.25, 0.5, 0.75, and 1, the solver finds the curve parameter whose x coordinate matches the requested input fraction and then evaluates y at that parameter. The solver first uses bounded Newton iterations and finishes with bisection when necessary, which keeps the result monotonic in x and avoids the common mistake of treating the local parameter t as elapsed time even when the control points make those values different.
The numeric samples therefore tell the truth about the curve. A row that shows input 0.5 paired with output 0.92 means the animation is already 92 percent of the way to its final position at the halfway mark of its duration, which is exactly what a strong ease-out curve should do and which the eye would also pick up from the Run preview. Repeatedly clicking Run lets you watch forward and reverse motion, which is useful for symmetric presets like ease-in-out where the reverse path mirrors the forward path and a mistake in either direction is easy to feel but hard to see in still numbers.
| Keyword | Equivalent cubic-bezier() | Character |
|---|---|---|
| linear | cubic-bezier(0, 0, 1, 1) | Constant speed across the full duration. |
| ease | cubic-bezier(0.25, 0.1, 0.25, 1) | Default CSS easing; mild acceleration, gentle deceleration. |
| ease-in | cubic-bezier(0.42, 0, 1, 1) | Slow start, accelerating toward the end. |
| ease-out | cubic-bezier(0, 0, 0.58, 1) | Fast start, decelerating into the final position. |
| ease-in-out | cubic-bezier(0.42, 0, 0.58, 1) | Slow at both ends, fastest at the midpoint. |
Keyword Presets Versus Explicit Coordinates
Choosing a keyword does not output the keyword itself. The visible declaration always exposes all four numbers, even when the curve came from the Linear, Ease, Ease-In, Ease-Out, or Ease-In-Out button. That choice matters because it keeps the output recordable, comparable across revisions, and modifiable without hidden defaults: you can copy the four numbers, tweak one of them by hand, paste the result back into the generator, and the preview updates immediately.
Numbers in the serialized output are rounded to three decimal places only when they leave the calculator; the sampling and solver retain full JavaScript numeric precision the entire time. So a control coordinate that internally evaluates to 0.3333333... will display in the copy as 0.333, but the sample table and Run preview still reflect the unrounded value. Treating the rounded text as the working value is a habit worth avoiding in collaborative settings, where two designers rounding at different stages can produce identical-looking declarations with subtly different motion. MDN's cubic-bezier() reference covers the same rounding behavior from the browser side and is a useful cross-check when the declaration looks right but the motion does not.
Accessibility and Motion Considerations When You Ship the Curve
CSS permits curves that are technically valid but unpleasant or confusing in practice. Large overshoots can make controls appear to reverse direction, leave their container, or reveal content that was expected to remain clipped. Rapid oscillation between positive and negative y values can also trigger discomfort for viewers with vestibular sensitivity, even though the curve itself satisfies every constraint in the spec. The accessibility floor is the same as it is for any transition: keep important state changes understandable without motion, respect prefers-reduced-motion, and confirm that focus and pointer behavior remain predictable while an element is in motion.
The generator focuses on one cubic-bezier timing function and stops there. It does not produce keyframes, spring physics, the linear() stop list, steps(), duration, delay, iteration count, or a full transition shorthand. That scope is a feature rather than a limitation: the output stays auditable in a single line and easy to compare against the W3C specification. Anything else that the animation needs, such as duration, distance, and the property being animated, still has to be chosen in the surrounding CSS so the easing lands in a context where it can do useful work rather than fighting the surrounding motion.
For a deeper look, see What Details Matter When You Generate a CSS Toggle Switch.