How cubic-bezier() works
Every CSS transition and animation has a timing function that maps elapsed time to progress. cubic-bezier(x1, y1, x2, y2) describes that mapping as a curve that always starts at (0, 0) and ends at (1, 1). The four numbers are the two control points in between: P1 shapes the start of the motion and P2 shapes the end. Pull P1 along the time axis and the animation starts slowly; lift it steeply and it leaps off the mark. That is why ease-out, cubic-bezier(0, 0, 0.58, 1), rises sharply at first and flattens out, and ease-in, cubic-bezier(0.42, 0, 1, 1), does the reverse.
The keywords are just names for fixed curves: ease is cubic-bezier(0.25, 0.1, 0.25, 1), ease-in-out is cubic-bezier(0.42, 0, 0.58, 1), and linear behaves like cubic-bezier(0, 0, 1, 1). ease is also the default when you write a transition without a timing function, which is why so many interfaces share the same slightly front-loaded feel. Pick a keyword in the editor and the table shows how different they really are: at the halfway point in time, ease has already covered about 80% of the distance.
Why x stays in 0–1 but y can overshoot
The x axis is time, and time only moves forward. If a control point's x went below 0 or above 1, the curve could loop back so that one moment had two different progress values, which makes no sense for an animation. The specification therefore requires both x values to be between 0 and 1, and browsers throw the whole declaration away if they aren't. This editor clamps x so the CSS it gives you is always valid.
The y axis has no such limit. A y value above 1 makes the motion travel past its end point and settle back, as in easeOutBack, cubic-bezier(0.34, 1.56, 0.64, 1). A y below 0 makes it pull back before setting off, the anticipation you see in easeInBack. Use these sparingly: a small overshoot on a button press or toast feels lively, but on a menu or a page transition it tends to look like a glitch. Note that overshoot can push an element outside its container, and for opacity the extra range is simply clipped.
Choosing an easing for interface motion
Nielsen Norman Group's guidance on animation duration and motion sums up the common pattern. Things entering the screen should ease out, arriving quickly and slowing as they come to rest, because the user is waiting for them. Things leaving should ease in, starting gently and accelerating away, because the user has already finished with them. For timing, NN/g suggests most UI animations fall between about 100 and 500 ms, with 100–400 ms right for most, and exits slightly shorter than entrances. A third common rule: an element that moves from one place on screen to another reads best with an ease-in-out curve, which accelerates and then brakes like a physical object.
In practice that means short durations with a strongly decelerating curve for most entrances. easeOutExpo or easeOutQuart at 200–300 ms makes a panel or dropdown feel responsive without a visible slow start. Reserve longer durations for large surfaces that travel a long way. Whatever you choose, apply it consistently through a custom property, for example --ease-out: cubic-bezier(0.16, 1, 0.3, 1);, so the whole interface moves with one character. Browse our library of CSS animations for effects to try your curve on, and see the native dialog animation recipe for a real entrance and exit pair.
Easing also affects how smooth a transition looks. A curve that changes speed sharply concentrates movement into fewer frames, so any jank is more visible. Animate transform and opacity where you can; our guide to CSS animation performance explains why. And honour prefers-reduced-motion: replace large movement with a fade, or remove it, for visitors who ask for less motion. The preview on this page follows the same rule and won't start by itself when that setting is on.
Springs and bounces with linear()
A cubic Bézier has only two control points, so it can bend in limited ways: it can overshoot at the end or dip at the start, but it cannot oscillate. Springs that wobble before settling and bounces that hit the floor several times need more. The linear() function covers that. It takes a list of progress values, each with an optional position in time, and joins them with straight lines: linear(0, 1.2 60%, 0.9 80%, 1) overshoots, dips back below the target and settles. With enough points, any curve can be approximated closely, which is how spring generators export their output.
According to MDN's compatibility data, linear() works in Chrome and Edge 113, Firefox 112 and Safari 17.2 onwards. The generator above samples your curve and keeps only the points needed to stay within about 0.001 of it, usually 18 to 35 stops. That is useful as a starting point for hand-tuning, or for adding a bounce after your curve. For a plain curve, cubic-bezier() is exact and shorter, and has worked in every browser for over a decade. To support older Safari with linear(), declare a cubic-bezier() fallback first and the linear() version after it; browsers that don't understand the second declaration keep the first.
steps() for frame-by-frame motion
The third kind of timing function doesn't interpolate at all. steps(4, jump-end) divides the duration into four equal parts and jumps between values, which suits sprite-sheet animation, a ticking clock hand or a typewriter effect. The second argument sets where the jumps happen: jump-start jumps at the beginning of each interval, jump-end (the default) at the end, jump-none holds both the first and last values, and jump-both adds a jump at each end. The shorthands step-start and step-end mean steps(1, jump-start) and steps(1, jump-end).