Easing

CSS easing functions explained, with cubic-bezier values

What ease, ease-in, ease-out and linear really do, how the four cubic-bezier numbers work, steps(), overshoot, named curves and which easing to use when.

· 6 min read

An easing function decides how an animation spends its time. Two animations can move the same distance in the same 600 ms and feel completely different: one lands softly, one feels mechanical, one overshoots and settles. The only thing that changed is the easing.

This page is a reference for the easing functions CSS gives you: the five keywords and their exact cubic-bezier() values, how the four numbers work, steps(), overshoot curves, a table of well-known named curves, and practical rules for picking one. For springs and bounces, which a Bezier curve cannot draw, see the companion article on the CSS linear() function.

linear
ease
ease-in
ease-out
ease-in-out
easeOutBack
steps(6)
Same distance, same 1.2 s, different easing

Each ball travels the full track in 1.2 seconds. Halfway through the time, they are in very different places: ease-in has covered about 31% of the distance, ease-out about 68%, the default ease about 80%, and easeOutBack has already passed the end and is about to come back. Each runner in the demo uses the easing named on its row:

.ez-run { width: calc(100% - 18px); }   /* track width minus the ball */
.ez-go .ez-run { animation: ez-move 1200ms var(--ez) 200ms both; }
@keyframes ez-move { to { transform: translateX(100%); } }

The keywords and their exact values

Every CSS easing keyword except linear is shorthand for a cubic Bezier curve. These are the values from the CSS Easing specification:

KeywordEquivalentFeels like
linearconstant speedMechanical. Right for spinners, progress, colour cycles
ease (default)cubic-bezier(.25, .1, .25, 1)Quick start, long gentle stop
ease-incubic-bezier(.42, 0, 1, 1)Slow start, full speed at the end
ease-outcubic-bezier(0, 0, .58, 1)Full speed at the start, slow landing
ease-in-outcubic-bezier(.42, 0, .58, 1)Slow at both ends, symmetric
step-startsteps(1, jump-start)Jumps to the end immediately
step-endsteps(1, jump-end)Holds, then jumps at the end

The keyword ease-in and ease-out curves are mild. Most design systems use stronger curves, which is why named curves like easeOutCubic exist.

How the four numbers work

cubic-bezier(x1, y1, x2, y2) draws a curve from (0, 0) to (1, 1). The horizontal axis is time, from the start of the animation to the end. The vertical axis is progress, from the start value to the end value. The four numbers are the two control points that bend the curve between those fixed ends:

The slope of the curve is the speed. A steep start means the value changes quickly at first; a flat end means it creeps into place. That is why ease-out, with its first control point at (0, 0) and second at (.58, 1), starts steep and ends flat.

The x values must be between 0 and 1, because time cannot go backwards. If you write cubic-bezier(1.2, 0, .5, 1), the declaration is invalid and the browser ignores it. The y values have no such limit.

Overshoot and anticipation

When a y value goes above 1, the curve rises past the end value before coming back: the element overshoots its target and settles. When a y value goes below 0, the element first moves slightly backwards, which animators call anticipation.

/* overshoot: y2 above 1 */
.pop { transition: transform 400ms cubic-bezier(.34, 1.56, .64, 1); }

/* anticipation: y1 below 0 */
.pull { transition: transform 400ms cubic-bezier(.36, 0, .66, -.56); }

A Bezier curve can overshoot once. It cannot wobble several times or bounce, because it has only two control points. For that you need linear() with many stops or a JavaScript spring.

Overshoot works best on transform. On opacity it does nothing visible above 1, and on width or height it causes layout to jump around the element.

steps()

steps(n, position) splits the animation into n equal jumps with no motion in between. It is how sprite-sheet animations, typewriter effects and ticking clocks are made.

PositionBehaviour
jump-end (default), endHolds the start value; the last jump lands at the end of the duration
jump-start, startJumps immediately; the end value is reached at the last step
jump-nonen values including both start and end, each held for an equal time
jump-bothJumps at both the start and the end, n + 1 jumps in total

The steps(6) row in the demo uses the default jump-end: six jumps, and the ball reaches the end of the track at the very end of the 1.2 seconds. For a sprite sheet with 8 frames of 64 px in a row, animate background-position-x from 0 to -512px (the full sheet width) with steps(8): each frame shows once, and the jump back to 0 at the end of each loop is the first frame again. With steps(8, jump-none) you would end at -448px, the last frame, instead.

Well-known named curves

Many design tools and animation libraries use the names from Robert Penner's easing equations. The originals are polynomial and exponential functions; the cubic-bezier versions below are the widely used approximations listed on easings.net.

Namecubic-bezierUse
easeOutQuad(.5, 1, .89, 1)Light, subtle entrances
easeOutCubic(.33, 1, .68, 1)A good default for anything entering
easeOutQuart(.25, 1, .5, 1)Stronger deceleration, panels and drawers
easeOutExpo(.16, 1, .3, 1)Very fast start, long settle. Popular for modals
easeOutBack(.34, 1.56, .64, 1)About 10% overshoot. Toasts, badges, snap-backs
easeInCubic(.32, 0, .67, 0)Elements leaving the screen
easeInOutCubic(.65, 0, .35, 1)Moving something from one place to another
easeInOutQuart(.76, 0, .24, 1)Same, with a more pronounced middle

Store the ones you use as custom properties so the whole interface shares one vocabulary:

:root {
  --ease-out: cubic-bezier(.33, 1, .68, 1);
  --ease-in: cubic-bezier(.32, 0, .67, 0);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-back: cubic-bezier(.34, 1.56, .64, 1);
}
.toast { transition: transform 300ms var(--ease-back), opacity 200ms var(--ease-out); }

Which easing to use when

When the same element enters and leaves, use different transitions for each direction. In CSS, put the entering transition on the open state and the leaving one on the base state, because the transition that applies is the one declared on the state being moved to.

Duration by distance and size

Easing and duration work together. A strong ease-out with a short duration feels snappy; the same curve over a second feels sluggish. These ranges are a reasonable starting point for interface motion:

ChangeDuration
Colour, opacity, small icon changes100 to 200 ms
Buttons, toggles, tooltips, small movements under 100 px150 to 250 ms
Dropdowns, cards, toasts, movement of a few hundred px250 to 400 ms
Modals, drawers, full-screen transitions300 to 500 ms

Larger elements and longer distances need more time, but not proportionally more: doubling the distance should add perhaps 30 to 50% to the duration. Exits can be about 20% shorter than entrances. On phones, where distances are smaller, stay near the low end.

Find the easing on a real site

When an animation on another site feels right, the curve is usually the reason, and it is hard to judge by eye. You can read it from Chrome DevTools, and this guide covers the cases where DevTools does not show it, such as JavaScript animations. The Capture Motion extension records the easing, duration and keyframes of every animated property on the page, from CSS, the Web Animations API, GSAP or Lottie, and gives them back as CSS, React or GSAP code, JSON, or a Figma Motion timeline.

Questions

What is the default easing in CSS?

ease, which is cubic-bezier(.25, .1, .25, 1). It applies to transitions and animations when you do not set a timing function. It starts a little fast and slows down for most of its length.

Can cubic-bezier values be negative or above 1?

The two y values can be any number, which is how you get anticipation (below 0) and overshoot (above 1). The two x values must stay between 0 and 1, or the whole declaration is invalid.

What is the difference between ease-out and ease-in?

ease-out starts fast and slows down at the end, so it suits things arriving. ease-in starts slow and speeds up, so it suits things leaving. The names describe which end of the motion is gentle.

How do I find the easing a website uses?

Inspect the element in Chrome DevTools and look at transition-timing-function or animation-timing-function, or use the Animations panel. The Capture Motion extension reads the curve of every animated property for you.