# CSS easing functions: what cubic-bezier actually does

> Learn how CSS easing works under the hood: the four cubic-bezier control points, what ease and ease-out really are, plus linear() and steps().

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-02 | Topics: [CSS](https://flaviocopes.com/tags/css/) | Canonical: https://flaviocopes.com/css-easing-cubic-bezier/

Every CSS transition and animation has an easing function. It decides *how* a value moves from start to end over time.

You've used `ease` and `ease-out` a hundred times. But what are they, really?

They're all `cubic-bezier()` curves. Once you understand the four numbers inside, you can read any easing value and design your own.

## The curve

Picture a graph. Time goes on the horizontal axis, from 0 to 1. Progress goes on the vertical axis, also from 0 to 1.

A straight diagonal line means constant speed: at 50% of the time, the animation is 50% done. That's `linear`.

Easing functions bend this line. A curve that starts flat and gets steep means the animation starts slow and speeds up. A curve that starts steep and flattens out means it starts fast and slows down.

## The four control points

`cubic-bezier()` takes four numbers:

```css
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
```

These are the coordinates of two points: `(x1, y1)` and `(x2, y2)`.

The curve always starts at (0, 0) and ends at (1, 1). Those two points in between are handles that pull the curve towards them, like the pen tool in a design app.

- the first point shapes the *start* of the curve
- the second point shapes the *end*

A y value higher than the x value pulls the curve up (faster progress early). A y value lower than x pulls it down (slower progress early).

The x values must stay between 0 and 1, because time can't go backwards. The y values can go below 0 or above 1, and that's where things get fun. More on that in a moment.

## The keywords are just bezier curves

The named easings map directly to `cubic-bezier()` values:

```css
ease         /* cubic-bezier(0.25, 0.1, 0.25, 1) */
ease-in      /* cubic-bezier(0.42, 0, 1, 1) */
ease-out     /* cubic-bezier(0, 0, 0.58, 1) */
ease-in-out  /* cubic-bezier(0.42, 0, 0.58, 1) */
```

Look at `ease-in`: the first point is (0.42, 0). The y is 0 while x is 0.42, so the curve stays flat at the start. Slow start, fast finish.

`ease-out` is the mirror: (0.58, 1) as the second point means the curve flattens at the end. Fast start, slow finish.

`ease` is not symmetric. It accelerates quickly and decelerates for most of its duration. That's why it feels more natural than `ease-in-out` for many UI movements.

## Why linear feels robotic

Nothing in the physical world moves at constant speed. A ball you throw decelerates. A door you push accelerates then slows down as you catch it.

Our brains expect that. When a box slides across the screen at perfectly constant speed, it reads as mechanical, like a printer head.

My advice: use `ease-out` for elements entering the screen. They arrive fast and settle gently, which feels responsive. Use `ease-in` for elements leaving, and `ease-in-out` for elements that move from one place to another.

Reserve `linear` for things that genuinely are mechanical: loading spinners, marquees, progress bars.

You can compare all of these side by side in the [easing preview tool](https://flaviocopes.com/tools/easing-preview/), which draws the curve for each function and animates them together so you can *feel* the difference.

## Going beyond 0 and 1: overshoot

Remember how the y values can exceed the 0–1 range? That's how you get overshoot.

```css
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
```

The second point has y = 1.275. The curve goes *past* the final value before coming back. A box scaling up will grow slightly too big, then snap back to its final size.

This is the classic "back" easing, and it makes buttons and modals feel springy without any JavaScript.

A negative y on the first point does the opposite: the element pulls back slightly before moving forward, like winding up before a throw:

```css
transition-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
```

## The linear() function

A cubic bezier curve can only bend so much. It can overshoot once, but it can't bounce three times.

That's what the newer `linear()` function is for. Despite the name, it's not constant speed. It lets you define easing as a series of points, and the browser connects them with straight lines:

```css
animation-timing-function: linear(
  0, 0.36, 0.63, 0.85, 1,
  0.88 65%, 1, 0.98 85%, 1
);
```

With enough points, those straight segments approximate any curve, including a real bounce or a spring simulation. This used to require JavaScript animation libraries. Now it's a CSS value.

Each value is the progress at that moment, and the optional percentage sets when it happens. Values without a percentage are spaced evenly.

You won't write these by hand. Tools generate the point list from a spring or bounce formula, and you paste the result in.

## steps()

The last easing family is `steps()`. Instead of a smooth curve, it jumps between a fixed number of frames:

```css
animation-timing-function: steps(8);
```

The animation holds still, then snaps to the next position, 8 times total.

This is how you animate a sprite sheet, or build a typewriter effect, or a ticking clock hand:

```css
.second-hand {
  animation: tick 60s steps(60) infinite;
}

@keyframes tick {
  to {
    transform: rotate(360deg);
  }
}
```

The hand jumps once per second instead of sweeping. Same keyframes, completely different feel, all from the timing function.

## Where easing applies

One thing that trips people up: easing applies **between keyframes**, not across the whole animation.

If your animation has keyframes at 0%, 50% and 100%, an `ease-out` timing function eases out twice — once per segment. Keep that in mind when an animation with several keyframes feels strangely jerky.

If you want a refresher on where these timing functions plug in, I wrote about the basics in [CSS transitions](https://flaviocopes.com/css-transitions/) and [CSS animations](https://flaviocopes.com/css-animations/). The timing function is the same property in both, and it's the single biggest lever you have on how motion feels.
