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
| Interaction | Duration | Easing | Reasoning |
|---|---|---|---|
| Button hover | 0.15–0.2s | ease-out | Fast, responsive feel |
| Color/background | 0.2–0.3s | ease | Smooth state change |
| Modal open | 0.3–0.5s | ease-out | Enters fast, settles gently |
| Modal close | 0.2–0.3s | ease-in | Accelerates out of view |
| Drawer/sidebar open | 0.3s | cubic-bezier(0.2,0,0,1) | Snappy, native feel |
| Dropdown menu | 0.2s | ease-out | Quick, responsive |
| Card hover lift | 0.25s | ease-out | Natural lift feel |
| Hamburger → X | 0.3s | ease-in-out | Symmetric transformation |
| Success/bounce | 0.5–0.7s | cubic-bezier(0.34,1.56,0.64,1) | Playful overshoot |
| Loading spinner | 1s | linear | Constant mechanical spin |
| Skeleton shimmer | 1.5s | ease-in-out | Wave-like, soothing |
| Hero section entrance | 0.6–1s | ease-out | Dramatic, 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; }