A CSS cubic-bezier() easing curve is defined by exactly four numbers — x1, y1, x2, and y2 — that describe two intermediate control points between the fixed start at (0, 0) and the fixed end at (1, 1). These four numbers are written directly into the transition-timing-function or animation-timing-function property, where they tell the browser how to map elapsed time to animated progress. Because the start and end points never move, the only thing the curve can change is the shape of the journey in between: a slow start, a snappy finish, a gentle deceleration, an overshoot past the end, or a sharp anticipation back before the motion begins. Every visual easing style in CSS, from the familiar ease keyword to a custom spring-like motion, is just a different choice of those four coordinates. The cubic-bezier() function is therefore the single most precise tool a front-end developer has for shaping how a UI element feels when it moves.
Two reference points are essential before working with the curve. The first is the W3C CSS Easing Functions Level 1 specification, which defines the syntax, validity rules, and keyword mappings. The second is the MDN page for cubic-bezier(), which documents browser behavior and shows practical examples. The two sources agree on every detail that affects the four numbers you will be entering.

Inside the Cubic Bezier Easing Function
A cubic Bézier curve in CSS is a parametric curve traced between four points. Two of those points — (0, 0) and (1, 1) — are pinned permanently. They represent the moment animation begins (zero time, zero progress) and the moment it finishes (full time, full progress). The remaining two points — (x1, y1) and (x2, y2) — are the values you supply as arguments to cubic-bezier(), and they pull the curve away from a straight line.
Visually, those two control points act like magnets. A control point pulled toward the top-left corner makes the value start fast and ease out. A control point pulled toward the bottom-right corner makes the value start slow and accelerate. Two opposing control points create an S-curve with a soft middle and sharp ends. Because only the y coordinate of each control point actually changes the animation's progress, the visual story of the easing is essentially the story of y1 and y2.
The Four Control Points and Their Limits
The CSS Easing Functions Level 1 specification is strict about x values and permissive about y values. The two x coordinates must each lie in the closed interval [0, 1] — anything outside that range makes the cubic-bezier() value invalid and the browser will fall back to its default. The y coordinates, however, may be any finite number, including negatives and values above one. Negative y produces anticipation (the animated value appears to pull back before moving forward), and y above one produces overshoot (the value passes the destination, then settles).
The five standard easing keywords all map to specific cubic-bezier() values defined in the same specification. The table below records the canonical control points so the relationship between keyword and curve is easy to audit.
| CSS keyword | x1 | y1 | x2 | y2 | Typical feel |
|---|---|---|---|---|---|
| linear | 0 | 0 | 1 | 1 | Constant speed from start to end |
| ease | 0.25 | 0.1 | 0.25 | 1 | Gentle start, soft finish (the default) |
| ease-in | 0.42 | 0 | 1 | 1 | Slow start, accelerating finish |
| ease-out | 0 | 0 | 0.58 | 1 | Fast start, decelerating finish |
| ease-in-out | 0.42 | 0 | 0.58 | 1 | Symmetric slow ends, fast middle |
These mappings are part of the spec, so any generator that claims CSS compatibility must agree with them exactly. The CSS Cubic Bezier Generator uses them as reference data: choosing a keyword loads the same control points, and the visible declaration always shows all four numbers rather than a hidden keyword.
Designing the Curve With the Generator
The fastest way to land on a working cubic-bezier() value is to adjust the four control numbers against immediate visual feedback. The following workflow uses the CSS Cubic Bezier Generator directly in a browser tab.
- Open the generator and pick a starting point. If you already know you want a feel similar to one of the standard keywords, click that keyword button to load its canonical control points. If you are starting from scratch, leave the inputs at any sensible defaults.
- Adjust the four control numbers. Enter or drag x1, y1, x2, and y2 toward the shape you want. Remember the rule from the spec: x1 and x2 must stay between 0 and 1, while y1 and y2 can range from −10 to 10. Numbers update live as you type.
- Inspect the graph. The square chart plots the parametric curve from the fixed start at the bottom-left to the fixed end at the top-right. Curves outside the square mean the y values are producing overshoot or anticipation, which can be intentional.
- Read the sample table. The table shows animated progress at input times of 0, 0.25, 0.5, 0.75, and 1. Use it to confirm the curve produces the rough shape you expect at the quarter, half, and three-quarter marks.
- Run the animated preview. Toggle the run preview to move a circle horizontally over 900 milliseconds using your current curve. Click repeatedly to compare forward and reverse motion at the same easing.
- Copy the complete declaration. Use the copy CSS button to write transition-timing-function: cubic-bezier(...); to the clipboard. The declaration includes its semicolon so it can be pasted into a stylesheet directly.
- Test the easing in the real component. Drop the declaration into the actual transition or animation you are building, with the real property, distance, and duration. If the motion does not feel right, return to step 2 and tune again.
Reading the Sample Table and the Animated Preview
The sample table answers a question the curve diagram alone cannot: how far has the animated value actually progressed when a given fraction of the duration has passed? For each input time, the solver finds the curve parameter whose x coordinate matches that time, then evaluates the y coordinate at that parameter. This distinction matters because CSS animation runs on input time, not on curve parameter. A curve with skewed x control points will not place half of its motion at the half-duration mark.
The animated preview takes the same computed curve and applies it to a 900-millisecond horizontal transition. The preview demonstrates timing, not production performance. A real component can feel different once the property, distance, duration, rendering cost, or input device changes. Use the preview to compare curves against each other and to confirm the rough shape of the motion, then verify with the live component before shipping.
If a curve deliberately extends outside the visible square, the chart does not mean the value was clamped. The square is kept at a stable zero-to-one frame so ordinary curves are easy to compare. The sample table and the moving circle both use the actual computed easing, so overshoot will still appear in the table as a y value above 1 and in the preview as the circle briefly landing past its destination.
Applying the Timing Function in a Real Transition
Once the curve feels right in the preview, paste the declaration into the production stylesheet. The cubic-bezier() value is used inside transition-timing-function for state transitions and inside animation-timing-function for keyframe animations. A common pattern looks like this:
transition: transform 350ms cubic-bezier(0.22, 0.61, 0.36, 1);
That single line sets the property, the duration, and the easing in one place. The generator does not produce the full transition shorthand, the duration, the delay, or any other parts of the animation. You remain in control of those values, which is intentional: the right duration for a 16-pixel slide is rarely the right duration for a 400-pixel slide, even with the same curve.
For keyframe animations the same value works in animation-timing-function. The easing applies between every pair of keyframes, which can produce very different motion from a single transition. When using a custom curve on an animation, preview the entire loop rather than just the first segment.
Overshoot, Reduced Motion, and Usable Curves
Valid CSS does not automatically mean appropriate CSS. A cubic-bezier() with strong overshoot can make a button appear to bounce off its container, leave a panel briefly, or reveal content that was expected to stay clipped. When motion carries meaning — confirming a click, signalling completion, communicating state — too much overshoot undermines the message. When motion is decoration, overshoot can be charming or distracting depending on context.
Respect the user's reduced-motion preferences. The prefers-reduced-motion media query can switch a custom easing back to linear or to a very short transition, which keeps state changes understandable without large movements. A valid curve is not automatically accessible, performant, or appropriate; it is only numerically well-formed.
Two more practical limits to keep in mind. The generator constrains x inputs to the closed [0, 1] range and accepts finite y values between −10 and 10, and the underlying calculation independently revalidates those constraints before producing output. Numbers are rounded to three decimal places only when the declaration is serialized to text; the in-browser calculation retains full JavaScript precision. All calculation happens locally, and no curve values, declarations, or interaction data are uploaded to any animation service, which makes the tool safe to use on proprietary design specs and internal color systems.
If you're weighing options, CSS Create Toggle Button With Exact Geometry covers this in detail.