Cubic Bezier and Easing Functions in CSS

CSS animations and transitions use cubic Bezier curves to control timing. Understanding how these curves work lets you create animations that feel natural, bouncy, snappy, or anything in between.

The Basics

Every CSS transition has a timing function that maps time to progress. Given a normalized time value (0 to 1), the function returns how far along the animation should be (also 0 to 1).

css
.element {
  transition: transform 0.3s ease-out;
  /*                         ^^^^^^^^ timing function */
}

A linear timing function means constant speed—50% of the time equals 50% of the progress. But real-world motion isn't linear. Objects accelerate and decelerate. This is where easing functions come in.

The cubic-bezier() Function

CSS provides the cubic-bezier() function for custom timing curves:

css
.element {
  transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}

The four numbers define two control points: cubic-bezier(x1, y1, x2, y2).

1 0 time P1 (0.25, 0.1) P2 (0.25, 1) start end
ease
cubic-bezier(0.25, 0.1, 0.25, 1)

The curve always starts at (0, 0) and ends at (1, 1). The two control points (P1 and P2, shown in red) shape the curve between them. The x-values represent time; the y-values represent progress.

How It Works

The cubic Bezier formula combines four points using basis functions (similar to Hermite interpolation, but with a different formulation):

P(t) = (1-t)³·P0 + 3(1-t)²t·P1 + 3(1-t)t²·P2 + t³·P3

Where:

  • P0 = (0, 0) — always the start
  • P1 = (x1, y1) — first control point
  • P2 = (x2, y2) — second control point
  • P3 = (1, 1) — always the end

The browser evaluates this parametrically, then for any given time value, finds the corresponding progress value on the curve.

The Bernstein Basis Functions

The cubic Bezier curve uses Bernstein polynomials as its basis functions:

1 0 0 0.5 1 t weight
B₀ = (1-t)³
B₁ = 3(1-t)²t
B₂ = 3(1-t)t²
B₃ = t³

Each basis function determines how much its corresponding control point influences the curve at any given t:

  • B₀ = (1-t)³ — Weight of P0 (start point). Strongest at t=0, fades to zero.
  • B₁ = 3(1-t)²t — Weight of P1 (first control). Peaks around t≈0.33.
  • B₂ = 3(1-t)t² — Weight of P2 (second control). Peaks around t≈0.67.
  • B₃ = t³ — Weight of P3 (end point). Zero at start, strongest at t=1.

Notice that at any value of t, the four functions sum to exactly 1. This ensures the curve stays bounded by its control points.

Easing Functions Visualized

1 0 time
linear
linear
1 0 time
ease
cubic-bezier(0.25, 0.1, 0.25, 1.0)
1 0 time
ease-in
cubic-bezier(0.42, 0, 1, 1)
1 0 time
ease-out
cubic-bezier(0, 0, 0.58, 1)
1 0 time
ease-in-out
cubic-bezier(0.42, 0, 0.58, 1)
1 0 time
snappy
cubic-bezier(0.2, 0, 0, 1)
1 0 time
overshoot
cubic-bezier(0.34, 1.56, 0.64, 1)
1 0 time
anticipation
cubic-bezier(0.68, -0.55, 0.27, 1.55)

The linear() Function (CSS Level 2)

Modern CSS adds linear() for complex multi-stop easing. Unlike cubic-bezier() which is limited to smooth curves with two control points, linear() lets you define arbitrary curves by specifying a series of output values.

The Syntax

css
transition-timing-function: linear(output1, output2, output3, ...);

Each value represents the output (progress) at evenly-spaced input (time) points. The browser linearly interpolates between them.

For example:

css
linear(0, 0.25, 0.5, 0.75, 1)

This defines 5 points. Since there are 5 values, they're placed at:

  • 0 at t=0% (start)
  • 0.25 at t=25%
  • 0.5 at t=50%
  • 0.75 at t=75%
  • 1 at t=100% (end)

This happens to be a straight line—the same as linear. But you can use any values:

css
linear(0, 0.5, 0.5, 1)  /* pause in the middle */
linear(0, 1, 0, 1)       /* bounce back and forth */
linear(0, 1.2, 1)        /* overshoot */

Explicit Stop Positions

You can also specify where each point occurs:

css
linear(0, 0.5 30%, 1)

This places the middle value at 30% instead of 50%. Values without positions are distributed evenly between their neighbors.

The Math: Linear Interpolation

Between any two adjacent points, the browser uses simple linear interpolation:

output = y0 + (y1 - y0) × ((t - t0) / (t1 - t0))

Where:

  • t is the current time (0 to 1)
  • t0, t1 are the time positions of the surrounding points
  • y0, y1 are the output values at those points

For example, with linear(0, 0.8, 1):

  • At t=0.25: We're between point 0 (t=0, y=0) and point 1 (t=0.5, y=0.8)
  • Output = 0 + (0.8 - 0) × (0.25 / 0.5) = 0.4

Writing Your Own

To create a custom linear() easing:

  1. Define your mathematical function (e.g., a bounce, spring, or elastic curve)
  2. Sample it at regular intervals (more samples = smoother curve)
  3. Output the values as a comma-separated list

Here's JavaScript to generate a bounce curve:

javascript
function generateBounce(samples = 20) {
  const values = [];
  for (let i = 0; i <= samples; i++) {
    const t = i / samples;
    // Simple bounce: parabola that hits 1 at t=0.7, then settles
    let y;
    if (t < 0.7) {
      y = (t / 0.7) ** 0.5; // ease-out to peak
    } else {
      const bounceT = (t - 0.7) / 0.3;
      y = 1 - 0.2 * Math.sin(bounceT * Math.PI); // small bounce
    }
    values.push(y.toFixed(3));
  }
  return `linear(${values.join(", ")})`;
}

For physics-based animations, you can sample real spring/bounce equations:

javascript
function generateSpring(samples = 20, stiffness = 100, damping = 10) {
  const values = [];
  for (let i = 0; i <= samples; i++) {
    const t = i / samples;
    // Damped harmonic oscillator approximation
    const decay = Math.exp(-damping * t);
    const oscillation = Math.cos(stiffness * t * 0.1);
    const y = 1 - decay * oscillation;
    values.push(Math.max(0, Math.min(1.5, y)).toFixed(3));
  }
  return `linear(${values.join(", ")})`;
}
1 0 time
overshoot
cubic-bezier
1 0 time
bounce
linear()
1 0 time
spring
linear()

The first curve shows what cubic-bezier() can do—a single overshoot. The bounce and spring curves show what linear() enables: multiple oscillations that would be impossible with just two control points.

Bounce Effect

css
transition-timing-function: linear(
  0,
  0.004,
  0.016,
  0.035,
  0.063,
  0.098,
  0.141,
  0.191,
  0.25,
  0.316,
  0.391,
  0.473,
  0.563,
  0.66,
  0.766,
  0.879,
  1,
  0.891,
  0.813,
  0.766,
  0.75,
  0.766,
  0.813,
  0.891,
  1
);

This 25-point curve creates a bounce effect. The values rise to 1 at the 17th point (around t≈0.67), then oscillate: 0.891 → 0.75 → 0.891 → 1. Each bounce is smaller than the last.

Spring Physics

css
transition-timing-function: linear(
  0,
  0.009,
  0.035,
  0.078,
  0.141,
  0.223,
  0.324,
  0.446,
  0.586,
  0.746,
  0.923,
  1.114,
  1.032,
  0.984,
  0.969,
  0.984,
  1.016,
  1.008,
  0.996,
  1.002,
  1
);

This 21-point curve simulates a damped spring. Notice value 12 is 1.114—it overshoots by 11.4%. Then it oscillates around 1 with decreasing amplitude: 1.032 → 0.969 → 1.016 → 0.996 → 1.002 → 1.

Performance Note

The timing function itself has no performance impact—it's just a mathematical formula evaluated once per frame. What matters is what you're animating:

GPU-accelerated (fast):

  • transform
  • opacity

Triggers layout (slow):

  • width, height
  • top, left, right, bottom
  • margin, padding

Always prefer transform: translateX() over left for position animations.

Tools

  • cubic-bezier.com — Interactive curve editor with comparison
  • easings.net — Gallery of common easing functions
  • Chrome DevTools — Edit timing functions visually in the Styles panel

See Also