GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 CSS Easing Curves You Can Copy-Paste

A CSS animation moving at one constant speed looks robotic; ease it down at the end, or spring past the target and settle, and it reads as natural.

Auto-plays · click a tile to jump to its section · all nine in one zip

These nine aren't ranked by popularity — they're ordered by how much control each one hands over. Three curves an arriving element might use come first (01–03), then three springs that only linear() can draw, the kind cubic-bezier() can't touch (04–06), then two ways to reuse those exact values instead of retyping them everywhere (07–08), and last, a demo that runs four of them side by side so the differences read at a glance (09). To see these curves land on a real surface, the bento grid hover animations run on the same values, and reusing one curve from the component side is covered in CSS animations in React without a library.

01ease-out-quart curve

Where it fits

Put cubic-bezier(.25, 1, .5, 1) on a translateX and the element shoots out fast from the very first frame, then settles near the target with no overshoot at all.

ease-out-quart curve
Open in a new tab

01 · ease-out-quart curve — Measured area 3.2% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

cubic-bezier(.25, 1, .5, 1)transform

What to read in the code

It reads well on a modal or drawer opening, or a card arriving on screen.

$duration: $dur-loop;                   // 2s
$easing: cubic-bezier(.25, 1, .5, 1);   // ease-out-quart

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

02ease-in-out-back curve

What moves

cubic-bezier(.68, -.6, .32, 1.6) pushes its y-values past 0 and 1, so the element pulls back a little before it leaves, then overshoots the target and settles back after arriving.

ease-in-out-back curve
Open in a new tab

02 · ease-in-out-back curve — Measured area 6.664% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

cubic-bezier오버슈트transform

How it is built

It suits a toast or badge that wants to pop in with some personality.

$easing: cubic-bezier(.68, -.6, .32, 1.6);   // ease-in-out-back

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

03ease-out-expo curve

The moment it fires

cubic-bezier(.16, 1, .3, 1) covers most of the distance in the first few frames, then crawls the rest of the way so slowly it barely reads as moving at all.

ease-out-expo curve
Open in a new tab

03 · ease-out-expo curve — Measured area 5.573% · moving frames 21/23Original by GODRICH · SCSS and React ship in the zip · See the password

cubic-bezier급감속transform

How it works

Use it on a sidebar or dropdown that should feel like it snaps open fast.

$easing: cubic-bezier(.16, 1, .3, 1);   // ease-out-expo

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

04Bounce via linear()

Where bounce effect css fits

cubic-bezier() can only bend one way, so it can never make a value rise, fall, and rise again — the way a dropped ball actually behaves.

Bounce via linear()
Open in a new tab

04 · Bounce via linear() — Measured area 3.203% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

linear()표본점 25개transform

How to wire it up

Sampling Robert Penner's easeOutBounce formula into 25 points for linear() produces that real bounce, a shape cubic-bezier() alone cannot draw.

$easing: linear(0, .013, .053, .118, .21, .328, .473, .643, .84, .97, .875, .807, .766, .75, .761, .798, .861, .951, .973, .943, .939, .962, .995, .984, 1);

.stage.is-demo .puck {
  animation: drop $duration $easing infinite;
}
@keyframes drop {
  from { transform: translate(-50%, 0); }
  to   { transform: translate(-50%, $drop); }
}

05Gentle spring

Where this shows up

This linear() value is sampled 23 points deep from a damped-spring equation — damping ratio .62, angular frequency 7.0.

Gentle spring
Open in a new tab

05 · Gentle spring — Measured area 3.44% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

linear()감쇠 스프링transform

The core of it

It overshoots the target by about 8% and settles quickly, no drama, which is a good match for a settings panel sliding into place.

$easing: linear(0, .044, .153, .297, .452, .602, .736, .849, .937, 1.002, 1.045, 1.071, 1.082, 1.083, 1.077, 1.066, 1.053, 1.04, 1.028, 1.018, 1.009, 1.003, 1);

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

06Snappy spring

What happens on screen

Lower that same equation's damping ratio to .35 and raise its angular frequency to 9.0, and the curve overshoots its target by more than 30% before oscillating a couple more times — visibly springy.

Snappy spring
Open in a new tab

06 · Snappy spring — Measured area 6.622% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

linear()감쇠 스프링오버슈트

The idea behind it

It fits a like-button press or a card snapping back after a drag release.

$easing: linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1);

.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}
@keyframes move {
  from { transform: translate(0, -50%); }
  to   { transform: translate($travel, -50%); }
}

07SCSS easing map mixin

What it looks like

A single $easings Sass map holds every curve from this post by name, and @mixin ease($name) pulls the right one through map.get() so no other component ever has to paste a raw curve value again.

SCSS easing map mixin
Open in a new tab

07 · SCSS easing map mixin — Measured area 9.358% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

Sass map@mixinmap.get

Why it is done this way

It's the pattern a design system reaches for once several parts need to share one brand easing.

$easings: (
  "ease-out-quart":   cubic-bezier(.25, 1, .5, 1),
  "ease-in-out-back": cubic-bezier(.68, -.6, .32, 1.6),
  "spring-gentle":    linear(0, .044, .153, .297, .452, .602, .736, .849, .937, 1.002, 1.045, 1.071, 1.082, 1.083, 1.077, 1.066, 1.053, 1.04, 1.028, 1.018, 1.009, 1.003, 1),
  "spring-snappy":    linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1),
);

@mixin ease($name, $prop: animation-timing-function) {
  #{$prop}: map.get($easings, $name);
}

.stage.is-demo .puck {
  animation-name: move;
  animation-duration: $duration;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  @include ease("spring-snappy");   // pulls item 06's exact value out of the map
}

08Tailwind v4 --ease token

When to use it

Declaring --ease-spring as a plain :root custom property and dropping var(--ease-spring) into the easing slot of the animation shorthand reproduces Tailwind v4's @theme --ease-* token pattern without ever loading Tailwind itself.

Tailwind v4 --ease token
Open in a new tab

08 · Tailwind v4 --ease token — Measured area 6.524% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

CSS 커스텀 프로퍼티var()Tailwind v4

Worth noting

Reach for it when a project wants its easing named and reused like a utility class.

$easing: var(--ease-spring);

:root {
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
}
.stage.is-demo .puck {
  animation: move $duration $easing infinite alternate;
}

09Side-by-side easing compare

What you see

Quart, back, snappy spring, and bounce run over the same two seconds and the same distance, side by side, so the shape of each deceleration is obvious at a glance.

Side-by-side easing compare
Open in a new tab

09 · Side-by-side easing compare — Measured area 4.24% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

cubic-bezierlinear()비교

The key property

None of the four values are new — they're items 01, 02, 06, and 04, reused exactly as they are.

.puck--quart  { animation: move $duration cubic-bezier(.25, 1, .5, 1) infinite alternate; }
.puck--back   { animation: move $duration cubic-bezier(.68, -.6, .32, 1.6) infinite alternate; }
.puck--spring { animation: move $duration linear(0, .055, .197, .394, .612, .824, 1.009, 1.153, 1.25, 1.3, 1.307, 1.281, 1.231, 1.167, 1.099, 1.035, .982, .942, .916, .905, .907, .917, .935, .955, .976, .995, 1) infinite alternate; }
.puck--bounce {
  animation: move-once $duration linear(0, .013, .053, .118, .21, .328, .473, .643, .84, .97, .875, .807, .766, .75, .761, .798, .861, .951, .973, .943, .939, .962, .995, .984, 1) infinite;
}

Where does the vertical bounce track break?

What goes wrong

Item 04, the roughest of these css transition curves to tune, first fell 180px, not 132px. On the 480×300 desktop stage that looked completely fine — nothing overflowed, nothing clipped, and that first pass shipped without anyone noticing a problem. The trouble only showed up once this site's mobile check kicked in: shrinking the stage to a 320px width also shrinks its height by the same ratio, 320×300÷480, down to 200px, and a 180px vertical track plus its top and bottom dots blew past that 200px budget with room to spare. Rendering the small version and measuring the actual frames is what caught it — the automated overflow flag in this post's measurement file read true for item 04 alone, while every other item here, all of them sideways instead of vertical, read false. A sideways track has slack inside a 320px width that a vertical one never gets, because a vertical track runs straight into the narrower 200px height budget the moment it's tall enough to matter.

How to avoid it

The drift that appears once a single curve is shared across a whole product is treated as a system problem in the dark SaaS linear design system, where the values live as tokens.

Cutting the track to 132px, set once as a Sass variable rather than a number copied into several places, removed the overflow for good and flipped that flag back to false. Item 04 is also the only demo in this set that moves along a vertical axis at all — the other eight all run translateX sideways, which is exactly why the narrower mobile height budget never got exercised until this one bounced into it. Nothing else about the curve changed: the same 25-point linear() value drives the same 2-second loop, only the track it travels got shorter, and the 8px dot markers at each end stayed exactly the size they were before. Opening the zip asks for a password; it's wnxge56e, typed exactly as it reads here, and the files unlock. A vertical animation checked only against the usual horizontal breakpoint is an easy trap — the mobile viewport's height needs its own pass, the same way this one finally got one.

Variants worth trying

Variant Changed value Feel
Slower quart Item 01's cubic-bezier(.25, 1, .5, 1) swapped for item 03's cubic-bezier(.16, 1, .3, 1) The arrival stretches out instead of settling right away
Heavier back Item 02's -.6 / 1.6 control points pushed further, to -.9 / 1.9 Both the pull-back and the overshoot read as far more exaggerated
Calmer spring Item 06's damping ratio raised from .35 toward item 05's .62 Overshoot drops from 30%+ to roughly 8%, and the extra oscillation nearly disappears

Accessibility

What reduced motion leaves

All nine drop to animation: none under prefers-reduced-motion: reduce, and each one freezes on its arrival point in transform rather than some mid-motion frame — 196px or 188px along the track for 01, 02, 03, 05, 06, 07, and 08, 108px down for 04's drop, and each of the four lanes at its own endpoint for 09. This post exists to show the shape of a curve, so turning off motion removes exactly the thing being demonstrated, but the layout itself never shifts, since the final position is already written into transform either way.

What else to check

Browser support is the other limit worth knowing before copying any of this: cubic-bezier() has worked everywhere since CSS transitions shipped, but linear() only reached Chrome and Edge at version 113, Firefox at 112, and Safari at 17.2 — an older engine just falls back to whatever timing function sits earlier in the cascade instead of breaking outright. More keyframe basics live in 9 CSS keyframe animations, and the token pattern item 08 reproduces is covered in full in 9 Tailwind v4 animations.

FAQ

What's the real difference between cubic-bezier() and linear()?

cubic-bezier() draws one smooth curve from four numbers, and that curve can only bend once — it can never make a value rise, fall, and rise again. linear() takes as many points as it's given, so a curve sampled straight from a real formula, like items 04 through 06 here, carries over exactly instead of getting approximated.

Does CSS have a built-in spring easing keyword?

No, not yet — a dedicated spring() easing function hasn't landed in browsers. Until it does, the practical way to get spring motion in CSS is to solve a damped-spring equation directly, the way items 05 and 06 do here, and sample the result into linear().

Is there a css easing generator that gets these values without the math?

For cubic-bezier(), a named-preset list like easings.net covers most everyday curves, and both Chrome's and Firefox's DevTools animation panels let you drag a curve and read off the numbers live. linear() values sampled from a physical formula, like the springs and bounce in this post, are more accurately produced by solving the equation in Python or JavaScript than by eyeballing a curve editor. For the spring feel without the derivation, the CSS bounce animations arrive with these curves already applied.

Enter the archive password

The password is inside this article. You will find it as you read.