Every CSS transition and animation has an easing curve — a mathematical function that controls how the animated value changes over time. The horizontal axis represents time (0 = start, 1 = end of duration) and the vertical axis represents the animated value's progress (0 = start value, 1 = end value). A straight diagonal line means linear motion — constant speed throughout. Any curve away from that diagonal introduces acceleration and deceleration, which is what makes animated UIs feel physical and alive rather than mechanical.
The cubic-bezier() function is the CSS mechanism for defining any custom easing curve. It describes a cubic Bezier curve using two control points — P1 and P2 — which pull the curve toward them like magnets without the curve ever passing through them. P0 is always fixed at (0,0) and P3 is always fixed at (1,1). You only specify P1 and P2 as four values: cubic-bezier(x1, y1, x2, y2).
The X values of P1 and P2 must stay between 0 and 1 — they represent points in time and can't go before the start or after the end of the animation. The Y values, however, can go anywhere. A Y value above 1 means the animated property temporarily exceeds its final value before settling — this is the overshoot that produces spring and bounce effects. A Y value below 0 means the property dips below its starting value before moving toward the end — this is the anticipation or "windup" effect.
The position of P1 controls the beginning of the curve. A P1 that's high on the Y axis means the animation starts fast (the curve climbs steeply from the origin). A P1 that's low means it starts slow. The position of P2 controls the end of the curve — a P2 high on the Y axis produces a slow finish; a P2 low means a fast finish. The ease-out family has P1 near the top-left (fast start) and P2 in the middle. The ease-in family has P1 near the origin (slow start) and P2 toward the top-right.
Once you know how to read a Bezier curve diagram, you can predict exactly how an animation will feel just by looking at it. A steep slope at the start means fast initial movement; a shallow slope means slow start. A curve that rises above the Y=1 line means the animation overshoots. A curve that dips below Y=0 means anticipation. An S-curve — gentle on both ends with a steep middle — is the ease-in-out family. A straight line from corner to corner is linear. The canvas above lets you drag the control points and see the curve update in real time — the most intuitive way to develop an intuition for how these curves work.
CSS provides five keyword aliases that map to specific cubic-bezier values. ease is the browser default — it has a quick start and a very gradual deceleration, and works acceptably for most generic UI transitions. ease-out has the fastest start and slowest end, making it the best choice for elements arriving from off-screen or appearing on hover, because it mimics the physics of an object arriving and settling. ease-in is the reverse — slow start, fast end — which is right for things exiting, because it mimics building momentum. ease-in-out is symmetric and good for transitions between two comparable states, like expanding and collapsing a panel. linear has no easing at all, which makes it feel mechanical — it's best for progress indicators that represent real measured progress, not motion design.
The design community has converged on several named custom curves that appear everywhere in modern UI. The Material Design standard uses cubic-bezier(0.4, 0, 0.2, 1) for between-state transitions (slightly asymmetric, feels decisive), cubic-bezier(0, 0, 0.2, 1) for entries (fast arrival), and cubic-bezier(0.4, 0, 1, 1) for exits (slow build into speed). The "expo out" curve cubic-bezier(0.16, 1, 0.3, 1) is extremely popular for sidebar and drawer animations — it feels snappy and modern. Spring curves like cubic-bezier(0.34, 1.56, 0.64, 1) are widely used for card and modal entries because the slight overshoot feels tactile and alive.
The steps() function is not a curve at all — it produces discrete jumps instead of continuous interpolation. It takes two arguments: the number of steps and the direction. steps(8, end) divides the animation into 8 equal chunks, jumping at the end of each interval. steps(8, start) jumps at the beginning. The keyword shortcuts step-start and step-end are aliases for single-step versions. Steps are essential for sprite sheet animations (where you need to jump between frames, not interpolate between them), typewriter effects (each step reveals one character), and cursor blink effects.
CSS recently gained a linear() timing function that allows you to define a piecewise linear easing curve by specifying a series of progress values at specific time percentages. This unlocks two things that cubic-bezier cannot do: easing curves that change direction more than once (like a multi-bounce spring), and precise approximations of real physics simulations. Tools like the linear-easing generator can produce a linear() value from a spring or bounce simulation. Browser support reached Chrome 113, Firefox 112, and Safari 17.2 — sufficient for most production use today with a cubic-bezier fallback for older browsers.
The single most impactful rule is to match the curve's character to the physics of what the element represents. Things entering the viewport — modals appearing, toasts sliding in, dropdowns opening — should use ease-out variants because the element is "arriving." Things leaving — modals closing, menus collapsing — should use ease-in variants because the element is "departing." Things transforming between two states of equal weight — a toggle, an expanding card, a tab change — work best with ease-in-out. Things that need to feel playful, tactile, or fun benefit from a spring curve with slight overshoot. Things that need to feel fast and precise — like a command palette or a quick contextual tooltip — should use an expo-out with a very short duration.
The cubic-bezier() function works identically in transition-timing-function and animation-timing-function. In keyframe animations, you can even specify a different timing function per keyframe by putting animation-timing-function inside the keyframe block — this means each segment of the animation can have its own easing, enabling complex orchestrated motion within a single @keyframes declaration. For example, a bouncing ball animation might use ease-in between the peak and the ground (accelerating under gravity) and ease-out between the ground and the peak (decelerating against gravity), all within one animation.
Chrome and Edge DevTools include a built-in cubic-bezier editor that you can access directly from the Styles panel. When inspecting any element that has a transition or animation-timing-function property, you'll see a small curved icon next to the timing function value. Clicking it opens an interactive curve editor nearly identical to the tool on this page. Changes made in DevTools apply live to the page, letting you tune your easing curve in context before copying the final value into your code. Firefox DevTools has a similar feature under the Animation panel.
Yes — only Y values can exceed the 0 to 1 range. X values must stay between 0 and 1 because they represent time, and time cannot go backward or forward beyond the animation duration. Y values beyond the normal range cause the animated property to temporarily exceed its target (overshoot for Y above 1) or dip below its start (undershoot for Y below 0). This is how spring effects and anticipation animations work in pure CSS.
ease is cubic-bezier(0.25, 0.1, 0.25, 1) — it has a fast start and very gradual deceleration, feeling snappy and natural for most UI interactions. ease-in-out is cubic-bezier(0.42, 0, 0.58, 1) — perfectly symmetric, equally slow at both ends, feeling deliberate and even-handed. For most hover effects and small interactions, ease feels more responsive. For panel slides and modal transitions, ease-in-out feels more intentional.
CSS cubic-bezier curves are fixed mathematical curves — they cannot respond to the element's current velocity when interrupted mid-animation. If a user triggers a reverse animation while the forward one is still running, a cubic-bezier transition simply starts over from the current visual position. JavaScript spring physics libraries like Framer Motion or GSAP can respond to interruptions by carrying over the current velocity into the new animation, producing much smoother interactive effects. For most page-level UI transitions, cubic-bezier is sufficient. For highly interactive, gesture-driven UIs, JavaScript springs feel significantly better.
Use steps() when you need discrete jumps rather than continuous motion — sprite sheet frame advancement, typewriter character reveals, cursor blinks, odometer-style number changes. Use cubic-bezier() for any animation where you want smooth continuous interpolation between the start and end values. The two are not interchangeable — steps deliberately prevents the browser from showing intermediate values, while cubic-bezier is specifically about controlling how those intermediate values are distributed over time.
Yes — the generator on this page shows a live canvas preview of the curve shape that updates as you drag the control points. You can also open Chrome or Edge DevTools, inspect any element with a transition, and click the curve icon next to the timing function value to open the browser's built-in curve editor. Additionally, the animated ball preview in DevTools shows the actual motion the easing will produce on a real element, which is more informative than the curve diagram alone.