codr.tools
Tools

Cubic-bezier easing generator

Draw a CSS easing curve by dragging its two control points, compare it against linear in a live preview, start from the keyword and named presets, and copy the cubic-bezier() value or a ready transition line.

Curve editor

P1P2

Across is time, up is progress. Drag P1 and P2, or focus one and use the arrow keys.

Overshoots, then settles back.

Control points

x is time and has to stay between 0 and 1. y is progress and may leave 0..1 to overshoot; this canvas draws -0.75 to 1.75.

A cubic-bezier() value, a keyword such as ease-out, or a name such as easeOutBack.

Preview

Your curvecubic-bezier(0.34, 1.56, 0.64, 1)

Comparisonlinear

The two dots start together and finish together in 1000ms. What differs is everything in between.

Progress runs from 0% to 110%. Outside 0 to 100% the property passes its target and comes back.

CSS

Easing value
cubic-bezier(0.34, 1.56, 0.64, 1)
Transition shorthand
transition: transform 1000ms cubic-bezier(0.34, 1.56, 0.64, 1);
Animation timing function
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);

Draw a CSS easing curve by dragging its two control points, compare it against linear in a live preview, start from the keyword and named presets, and copy the cubic-bezier() value or a ready transition line.

How to use this tool#

  1. Drag P1 and P2 on the canvas. Across is time, up is progress. P1 shapes the start of the motion and P2 shapes the end. Each handle also takes keyboard focus: arrow keys move it by 0.01, and Shift with an arrow key moves it by 0.1.
  2. Or type the numbers. The four fields mirror the handles, in the orderx1, y1, x2, y2. The x fields are held to 0 and 1 because CSS rejects anything else.
  3. Or paste what you already have. The paste field takes a full cubic-bezier(...) value, the four bare numbers, a keyword such as ease-out, or a name such aseaseOutBack. A transition-timing-function: prefix and a trailing semicolon are both fine.
  4. Start from a preset. The list holds the five CSS keywords with their spec values, then 24 named curves, from the gentle sine pair to the back curves that overshoot.
  5. Press Play. Two dots run the same distance over the same duration: yours on top, a comparison curve below, linear by default. Change the duration to see how much of the character comes from timing rather than shape.
  6. Copy the line you need. The raw easing value, a ready transition shorthand with the property and duration filled in, or theanimation-timing-function declaration.

Nothing is uploaded and nothing is stored. The curve is solved in your browser, by the same code the preview runs on.

What a cubic-bezier easing does#

A CSS transition or animation needs two things from you: how long it runs, and how the change is spread across that time. The duration fixes the endpoints. The easing function decides everything between them. Given the fraction of the duration that has passed, it returns the fraction of the change that should have happened by then.

cubic-bezier() writes that mapping as a curve. Time runs along the horizontal axis from 0 to 1, and progress runs up the vertical axis, normally also 0 to 1. A straight diagonal means half the time gives half the change, which is linear. Bow the curve and the property spends more of its time moving quickly at one end and slowly at the other.

That gives you a simple way to read any curve on the canvas above. Steep is fast, flat is slow. A curve that shoots up early and levels off is something arriving and settling. A curve that crawls then snaps upward is something building speed and leaving.

The four numbers, and why there are only four#

A cubic Bézier curve is drawn from four points. The CSS Easing specification fixes the first and the last: P0 sits at (0, 0), the moment nothing has happened yet, and P3 sits at (1, 1), the moment everything has. Those two never move, which is why an easing always starts at the start and finishes at the finish. The two you control are P1 and P2, and their coordinates are the four numbers:

css
cubic-bezier(0.34, 1.56, 0.64, 1)
             ^     ^     ^    ^
             |     |     |    +-- P2 y: progress, may leave 0..1
             |     |     +------- P2 x: time, 0..1 only
             |     +------------- P1 y: progress, may leave 0..1
             +------------------- P1 x: time, 0..1 only

The x of a control point is a position in time and the y is a position in progress. P1 pulls the beginning of the curve toward itself and P2 pulls the end toward itself.

The control points are not on the curve. They are magnets, not waypoints. This trips people up with the overshoot curves: easeOutBack has a P1 y of 1.56, which looks like a 56% overshoot, but the curve itself only reaches about 110%, and it peaks a little past the halfway point rather than at the end. The readout under the preview above gives the real range for whatever curve you have drawn.

Why x has to stay between 0 and 1#

MDN's formal syntax for the function iscubic-bezier( [ <number [0,1]> , <number> ] #{2} ): the first number in each pair is restricted, the second is not. In other words, x1 and x2 must be between 0 and 1, while y1 and y2 can be anything.

The reason is that x is time. For the curve to be usable as an easing, every moment has to map to exactly one progress value, and the browser has to be able to work backwards from a moment to a point on the curve. Keeping both control point x values inside 0 and 1 is what guarantees the curve never doubles back horizontally. Let x leave that range and the curve could give two answers for the same instant, or run time backwards, so the specification does not allow it.

Getting it wrong is not a warning, it is a dropped declaration. As MDN puts it: "If the cubic Bézier curve is invalid, CSS ignores the whole property." The element keeps whatever timing function it had, which for a transition that sets nothing else is the initial value,ease. That is the usual reason a hand-written curve seems to have no effect: it was never applied. The x fields in the tool above are clamped so you cannot produce one by accident.

Overshoot, and the properties that refuse it#

y is progress, and the specification puts no limit on it. MDN is explicit about what that buys you: "Cubic Bézier curves with the P1 or P2 ordinate outside the [0, 1] range can cause the value to go farther than the final state and then return. In animations, this creates a kind of 'bouncing' effect."

A y above 1 is overshoot: the property passes its target and comes back, which reads as weight and momentum. A y below 0 is anticipation: the property moves the wrong way first, like winding up before a throw. The named Back curves do one or both, and the canvas above leaves headroom above and below the 0 to 1 box so you can see them.

The catch is that only some properties can go past their target. A transform, atranslate or a margin has no natural ceiling, so a scale can pass 1 and come back. Properties with a fixed range clamp instead, and the overshoot is simply not drawn. MDN gives the concrete case: "a color component greater than 255 or smaller than 0 inrgb() will be clipped to the closest allowed value". The same goes foropacity, which cannot be more transparent than transparent.

The five keyword easings#

Four of the CSS keywords are cubic-bezier curves with fixed values, straight from the CSS Easing specification. The fifth, linear, is no longer defined as one.

  • ease is cubic-bezier(0.25, 0.1, 0.25, 1). The initial value oftransition-timing-function, so it is what you get when you set a duration and nothing else. It leaves quickly and takes a long time to settle.
  • linear is equivalent to cubic-bezier(0, 0, 1, 1), a straight diagonal. MDN notes that the keyword is now always interpreted aslinear(0, 1), the linear easing function rather than a Bézier curve. The result is the same constant rate.
  • ease-in is cubic-bezier(0.42, 0, 1, 1). Slow to start, fastest at the very end.
  • ease-out is cubic-bezier(0, 0, 0.58, 1). Fastest at the very start, settling at the end. The mirror of ease-in.
  • ease-in-out is cubic-bezier(0.42, 0, 0.58, 1). Eased at both ends and symmetric.

Load any of them from the preset list to see the shape, then drag a handle to make it yours. A keyword is not faster to render than the equivalent cubic-bezier(): it is the same curve either way, and the tool tells you when your curve has landed exactly on a keyword.

The named curves#

The other 24 presets are the named easings published by easings.net, in that project's own cubic-bezier values. They come in families, each with an in, an out and an in-out variant:

  • Sine, Quad, Cubic, Quart, Quint, Expo. A ladder from barely-there to severe.easeOutSine is a gentle settle; easeOutExpo, atcubic-bezier(0.16, 1, 0.3, 1), does almost all of its travel in the first third and then glides.
  • Circ. Built on a circular arc, so it is flat at one end and nearly vertical at the other. Dramatic, and easy to overdo.
  • Back. The overshoot family. easeOutBack passes the target and comes back, easeInBack winds up before it leaves, and easeInOutBack does both.

easings.net also publishes Elastic and Bounce, and those have no cubic-bezier value at all. A single cubic Bézier can change direction at most twice, which is enough for one overshoot but nowhere near enough for a curve that bounces four times. Those need keyframes, or thelinear() function below.

Choosing an easing in practice#

Most interface motion follows one rule, and Material Design's motion guidance states it directly: elements entering the screen use a decelerating curve, because they arrive already moving and settle into place, while elements permanently leaving use an accelerating curve, because they speed away and need less of your attention. Material's own values arecubic-bezier(0, 0, 0.2, 1) for the deceleration curve,cubic-bezier(0.4, 0, 1, 1) for the acceleration curve, andcubic-bezier(0.4, 0, 0.2, 1) as the standard curve for elements that move around without leaving.

  • Arriving: ease-out. Menus, dialogs, tooltips, anything appearing. The fast start means the interface responds immediately; the slow end means it lands rather than stops.
  • Leaving: ease-in. Shorter than the entrance. Material suggests around 195ms for an exit against 225ms for an entrance on mobile.
  • Moving on screen: ease-in-out or the standard curve. Something that starts and ends in view should ease at both ends.
  • Continuous rotation: linear. The one place a straight diagonal is right. A spinner with any easing in it visibly stutters once per turn.

Duration matters at least as much as shape. Material puts typical mobile transitions at about 300ms, up to 375ms for large ones, and desktop transitions at 150ms to 200ms, since a pointer covers the same distance faster than a finger. Below roughly 100ms a transition stops reading as motion; past about 500ms it starts to feel like waiting. Use the duration field above with a single curve to feel where those limits sit.

One practical note: the easing never changes how long the animation runs. Every curve above finishes at exactly the duration you set. What it changes is where the time is spent.

linear() and steps(), the other easing functions#

cubic-bezier() is one of three easing function families in CSS, and the other two do things it cannot.

linear() takes a list of output values with optional positions in time and joins them with straight lines. Its grammar islinear( [ <number> && <percentage>{0,2} ] # ). The first point defaults to 0% and the last to 100%, and anything in between is spaced evenly unless you give it a percentage. So linear(0, 0.25 75%, 1) spends three quarters of the duration creeping to a quarter of the change, then covers the rest in the last quarter of the time. With enough points it can approximate springs and bounces, which is exactly what a single cubic Bézier cannot do.

steps() jumps rather than glides. It takessteps(<integer>, <step-position>?), where the step position is one ofjump-start, jump-end, jump-none, jump-both,start or end. It is how sprite sheet animations and typewriter effects are built. The two extra keywords are shorthands for a single step:step-start is steps(1, jump-start) and step-end issteps(1, jump-end).

Pasting either of those into the field above gets you an explanation rather than a curve, because neither has a cubic-bezier equivalent.

Reduced motion#

Some people have the operating system set to reduce motion, often because movement on screen makes them ill. The browser reports that through theprefers-reduced-motion media query, and the honest response is not a gentler curve. A smaller movement is still a movement. Cut the distance, or cut the motion and keep the state change.

css
.drawer {
  transition: translate 300ms cubic-bezier(0.33, 1, 0.68, 1);
}

@media (prefers-reduced-motion: reduce) {
  .drawer {
    /* Not a gentler curve: no movement at all. */
    transition-duration: 1ms;
  }
}

A duration of 1ms rather than 0 keeps the transitionend event firing, so any JavaScript waiting on it still runs. The preview on this page follows the same rule: with reduced motion set, nothing plays until you press Play.

Worked examples#

An element arriving and leaving

The pair of Material curves, with the exit shorter than the entrance. Note the order inside thetransition shorthand: the first time value is the duration and a second one would be the delay.

css
/* Arriving: fast in, settling at the end. */
.panel {
  transform: translateY(0);
  transition: transform 225ms cubic-bezier(0, 0, 0.2, 1);
}

/* Leaving: slow off the mark, then gone. */
.panel[hidden] {
  transform: translateY(-8px);
  transition: transform 195ms cubic-bezier(0.4, 0, 1, 1);
}

A scale that overshoots

transform has no ceiling, so the curve's trip past 1 is visible. The same easing onopacity would be clipped at fully opaque and the overshoot would read as a pause.

css
@keyframes pop {
  from { transform: scale(0.8); }
  to   { transform: scale(1); }
}

.badge {
  animation: pop 320ms both;
  /* P1 y of 1.56 pulls the curve past 1: the badge grows a little
     beyond full size and settles back. */
  animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
}

Curves as custom properties

Naming a small set of curves and durations once keeps a codebase consistent, and it means changing the house easing is a one-line edit. Two or three curves is usually enough for a whole interface.

css
:root {
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
  --duration-fast: 150ms;
  --duration-base: 250ms;
}

.card {
  transition:
    transform var(--duration-base) var(--ease-out-quart),
    box-shadow var(--duration-fast) var(--ease-out-quart);
}

The same curve in JavaScript

The Web Animations API takes the identical string, so a curve you settle on here works in both places.

javascript
element.animate(
  [{ transform: 'translateX(0)' }, { transform: 'translateX(200px)' }],
  { duration: 400, easing: 'cubic-bezier(0.25, 1, 0.5, 1)', fill: 'both' },
);

Questions people ask#

Why is my cubic-bezier being ignored?

Almost always an x outside 0 to 1. MDN: "If the cubic Bézier curve is invalid, CSS ignores the whole property", so the declaration is dropped and the element falls back to whatever it had, usually ease. Paste the value into the field above: if it is out of range you get told which rule it broke. The other common causes are a unit that slipped in, such as1px or 50% where a bare number belongs, and a missing duration, since a transition with no duration never runs whatever its easing says.

Can I make a curve that bounces several times?

Not with cubic-bezier(). One cubic segment can change direction at most twice, which is one overshoot. For a real bounce or a spring, use linear() with enough points, or write the keyframes out.

Is cubic-bezier() slower than a keyword?

No. ease, ease-in, ease-out and ease-in-out are defined as specific cubic-bezier curves, so the browser does the same work either way. Keywords are shorter to read, which is the only reason to prefer them.

Does the easing change how long the animation takes?

No. The curve starts at (0, 0) and ends at (1, 1), so it finishes exactly when the duration says. It only redistributes the movement inside that window. If something feels too slow, change the duration, not the curve.

Why does my overshoot not show on a colour transition?

Because the result is clamped. A colour channel pushed past its limit is clipped to the closest allowed value, so the part of the curve above 1 produces no visible change and the transition appears to stall near the end. Keep overshoot for transforms. If you are choosing the two colours either side of that transition, the contrast checker will tell you whether the text on top still passes.

What do the numbers mean again, in one line?

cubic-bezier(x1, y1, x2, y2) places two magnets. x is time and must be between 0 and 1; y is progress and may go outside it for overshoot. The first pair shapes the start, the second pair shapes the end.

Does anything I type here leave my browser?

No. The page is a static file, the curve is solved by JavaScript running on your machine, and there is no backend to send anything to. Disconnect from the network and the editor keeps working.

  • Build linear, radial and conic CSS gradients visually: any angle, shape, size and centre, colour stops with their own position and opacity, repeating variants and an interpolation colour space, with the background declaration ready to copy.

  • Build a CSS box-shadow visually, with as many layers as you need: offsets, blur, spread, colour with opacity and inset, over a preview whose box colour, background and border radius you set yourself.

  • Build a CSS border-radius visually: one radius per corner or the elliptical slash syntax for organic blob shapes, with presets, a live preview, the shortest correct shorthand and the corner longhands.

  • Build a CSS Grid layout visually: set the columns, rows, track sizes and gaps, drag items across cells to place them, and copy the generated CSS and the matching HTML.