Skip to main content

CSS Easing Functions: Timing, Curves, and the Art of Motion

Easing functions control the rhythm of an animation — how speed changes over time. The same 0.3s transition on a button can feel snappy, bouncy, soft, or mechanical depending entirely on the easing function. Understanding them transforms generic motion into intentional, premium-feeling interactions.


1. What Easing Functions Control

/* The timing-function position in transition/animation shorthand */
transition: property duration timing-function delay;
transition: transform 0.3s ease 0s;

This is the easing function

Without a custom easing:

ease-in: slow ──────────────────►──────►──────► fast
ease-out: fast ►──────►──────────────────────── slow
linear: constant speed throughout
ease: slow → fast → slow (the browser default)

2. The 5 Built-In Keywords

/* ease: slow start, fast middle, slow end (browser default) */
transition: transform 0.3s ease;

/* linear: constant speed — mechanical, robotic feel */
transition: transform 0.3s linear;

/* ease-in: slow start → fast end — feels like "accelerating" */
/* Good for: things LEAVING the screen */
transition: opacity 0.2s ease-in;

/* ease-out: fast start → slow end — feels like "decelerating" */
/* Good for: things ENTERING the screen */
transition: transform 0.4s ease-out;

/* ease-in-out: slow start, fast middle, slow end — symmetric */
/* Good for: transitions where element stays on screen */
transition: transform 0.5s ease-in-out;

The Rule of Thumb for Enter/Exit

Element ENTERING (coming into view):
Use ease-out — fast start feels responsive, slows as it settles

Element LEAVING (going away):
Use ease-in — builds speed as it exits, feels purposeful

Element STAYING (moving position):
Use ease-in-out — smooth departure and arrival

3. cubic-bezier() — Custom Curves

All easing keywords map to cubic-bezier values. You can create any curve:

/* cubic-bezier(x1, y1, x2, y2) */
/* Controls two "handles" on the speed curve */

/* The keyword equivalents */
ease: cubic-bezier(0.25, 0.1, 0.25, 1.0)
linear: cubic-bezier(0.0, 0.0, 1.0, 1.0)
ease-in: cubic-bezier(0.42, 0.0, 1.0, 1.0)
ease-out: cubic-bezier(0.0, 0.0, 0.58, 1.0)
ease-in-out: cubic-bezier(0.42, 0.0, 0.58, 1.0)

/* Custom curves for different feels */

/* ── Snappy UI: quick response, fast settle */
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);

/* ── Smooth iOS-style */
--ease-ios: cubic-bezier(0.4, 0, 0.2, 1);

/* ── Material Design standard (Google) */
--ease-material: cubic-bezier(0.4, 0, 0.2, 1);
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1); /* Elements entering */
--ease-accelerate: cubic-bezier(0.4, 0, 1, 1); /* Elements leaving */

/* ── Bounce: y values can go above 1 or below 0 */
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
/* y1=1.56: overshoots past the end, then comes back */

/* ── Elastic: bigger overshoot */
--ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55);

/* ── Anticipate: slight pull-back before launch */
--ease-anticipate: cubic-bezier(0.36, -0.4, 0.64, 1.4);
/* Using them as custom properties */
:root {
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
--ease-smooth: cubic-bezier(0.25, 0.1, 0.25, 1);
}

.c-btn:hover { transform: translateY(-2px); transition: transform 0.2s var(--ease-snappy); }
.c-modal { transition: transform 0.4s var(--ease-bounce); }
.c-nav__link::after { transition: width 0.3s var(--ease-smooth); }

4. linear() — The Modern Easing Function

CSS linear() (2023) allows you to define arbitrary, multi-point easing curves — perfect for spring physics, bounces, and complex custom motions:

/* linear(v1, v2, v3 ...) */
/* Each value is the progress along the curve at evenly-spaced time points */

/* Spring-like bounce using linear() */
.c-popup {
animation: pop-in 0.6s
linear(
0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%,
0.723 12.9%, 0.938 16.7%, 1.017,
1.077, 1.121, 1.149 24.3%, 1.159,
1.163 27%, 1.154, 1.129 32.3%,
1.051 39.8%, 1.017 43.1%, 0.991,
0.977 51%, 0.974 53.8%, 0.975 61.6%,
0.990 68.1%, 1.001 79.4%, 1.004 88%,
1
)
both;
}

[!TIP] easings.net lets you browse and copy linear() values for spring, elastic, and bounce easings. The values above are from easings.net's "Spring" preset.


5. steps() — Discrete Animation

For sprite animations, typewriter effects, and mechanical motion:

/* steps(n, direction) */
/* n: number of steps
start: jump at the beginning of each step
end: jump at the end (default) */

/* Typewriter effect */
.c-typewriter {
width: 0;
overflow: hidden;
white-space: nowrap;
border-right: 2px solid var(--color-primary);
animation:
typing 3s steps(40, end) both, /* Reveal characters */
blink 0.75s step-end infinite; /* Blinking cursor */
}

@keyframes typing { from { width: 0; } to { width: 20ch; } }
@keyframes blink { 50% { border-color: transparent; } }

/* Sprite sheet animation (CSS sprites) */
.c-sprite-run {
width: 80px;
height: 80px;
background-image: url('sprite-sheet.png'); /* 8 frames of 80px each */
animation: run 0.8s steps(8, end) infinite;
}
@keyframes run {
from { background-position: 0; }
to { background-position: -640px; } /* 8 frames × 80px */
}

/* step-start = steps(1, start) */
/* step-end = steps(1, end) */
.c-blink {
animation: blink 1s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }

6. Easing in Keyframe Animations

Control timing per-keyframe step, not just per-animation:

/* animation-timing-function inside @keyframes controls segment easing */
@keyframes bounce-in {
0% { transform: scale(0); animation-timing-function: ease-out; }
50% { transform: scale(1.2); animation-timing-function: ease-in; }
70% { transform: scale(0.9); animation-timing-function: ease-out; }
85% { transform: scale(1.05); animation-timing-function: ease-in; }
100% { transform: scale(1); }
}

/* Each keyframe pair uses a different easing */
.c-card--pop {
animation: bounce-in 0.6s both;
/* 0%→50%: ease-out (decelerates into overshoot) */
/* 50%→70%: ease-in (accelerates back) */
/* 70%→85%: ease-out (small secondary overshoot) */
/* 85%→100%: inherits from animation-timing-function */
}

7. Real-World Easing Choices by Interaction Type

InteractionDurationEasingReasoning
Button hover0.15–0.2sease-outFast, responsive feel
Color/background0.2–0.3seaseSmooth state change
Modal open0.3–0.5sease-outEnters fast, settles gently
Modal close0.2–0.3sease-inAccelerates out of view
Drawer/sidebar open0.3scubic-bezier(0.2,0,0,1)Snappy, native feel
Dropdown menu0.2sease-outQuick, responsive
Card hover lift0.25sease-outNatural lift feel
Hamburger → X0.3sease-in-outSymmetric transformation
Success/bounce0.5–0.7scubic-bezier(0.34,1.56,0.64,1)Playful overshoot
Loading spinner1slinearConstant mechanical spin
Skeleton shimmer1.5sease-in-outWave-like, soothing
Hero section entrance0.6–1sease-outDramatic, cinematic feel

8. Easing Resources and Tools

Visualization + generation:
cubic-bezier.com — Interactive curve editor, copy CSS value
easings.net — Library of named easings + linear() values
linear-easing.glitch.me — Spring physics to linear() converter

Easing collections:
easings.co — Visual library with copy buttons
animista.net — CSS animation library with easing chooser
animate.style — Animate.css library for inspiration

AI prompting:
"Use ease-out for all enter transitions"
"Use cubic-bezier(0.34, 1.56, 0.64, 1) for bouncy interactive elements"
"Use linear for loading spinners and progress bars"

9. Easing Token System

/* Add easing to your design token system */
:root {
/* Duration */
--duration-instant: 0.1s;
--duration-fast: 0.2s;
--duration-normal: 0.3s;
--duration-slow: 0.5s;
--duration-slower: 0.8s;

/* Easing */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1); /* Material-like */
--ease-enter: cubic-bezier(0, 0, 0.2, 1); /* Enter: decelerate */
--ease-exit: cubic-bezier(0.4, 0, 1, 1); /* Exit: accelerate */
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); /* Springy */
--ease-snappy: cubic-bezier(0.2, 0, 0, 1); /* Snappy iOS-like */
--ease-linear: linear;

/* Pre-built transition combos */
--transition-fast: all var(--duration-fast) var(--ease-default);
--transition-normal: all var(--duration-normal) var(--ease-default);
--transition-enter: transform var(--duration-normal) var(--ease-enter),
opacity var(--duration-fast) var(--ease-enter);
--transition-exit: transform var(--duration-fast) var(--ease-exit),
opacity var(--duration-fast) var(--ease-exit);
}

/* Usage */
.c-btn { transition: var(--transition-fast); }
.c-modal { transition: var(--transition-enter); }
.c-toast { animation: slide-in var(--duration-normal) var(--ease-bounce) both; }