Articles /
Easing functions explained
The difference between animation that feels mechanical and animation that feels alive.
Why easing matters
Linear animation — constant speed from start to finish — feels robotic. Real objects accelerate and decelerate. Easing functions map time to progress on a curve, making motion feel physical. The right easing is often the difference between a UI that feels polished and one that feels like a prototype.
The built-in keywords
/* ease — slow start, fast middle, slow end (default) */
transition: transform 0.3s ease;
/* ease-in — slow start, fast end (entering the screen) */
transition: opacity 0.3s ease-in;
/* ease-out — fast start, slow end (most natural for UI) */
transition: transform 0.3s ease-out;
/* ease-in-out — slow start and end */
transition: transform 0.3s ease-in-out;
/* linear — constant speed (rarely what you want for UI) */
transition: width 0.3s linear;
For most UI transitions, ease-out is the best default — it responds immediately to input and decelerates to a stop, which matches how users expect objects to behave.
cubic-bezier() for custom curves
All keyword easings are shortcuts for cubic-bezier() curves. You define four control points that shape the timing:
/* Snappy — fast start, abrupt stop */
transition: transform 0.2s cubic-bezier(0.25, 0, 0, 1);
/* Spring-like overshoot */
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
/* Smooth deceleration */
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);
The spring-like cubic-bezier(0.34, 1.56, 0.64, 1) is popular for toggles, checkboxes, and micro-interactions where a slight bounce adds energy.
linear() for complex curves
The linear() function is new in 2024–2025 and lets you define multi-point easing — essentially any curve shape:
/* Bounce easing — impossible with cubic-bezier alone */
transition: transform 0.6s linear(
0, 0.36, 0.66, 0.89,
1, 0.97, 1, 0.99, 1
);
/* Spring with damping */
transition: transform 0.5s linear(
0, 0.22, 0.68, 1.12,
0.98, 1.01, 1
);
linear() is supported in Chrome 113+, Safari 17.2+, and Firefox 112+. It enables bounce, spring, and elastic effects that previously required JavaScript animation libraries.
steps() for frame-by-frame
Use steps() for sprite animations, typewriter effects, or any animation that should jump between discrete frames rather than interpolating smoothly:
/* Typewriter: reveal text character by character */
.typewriter {
animation: typing 3s steps(30, end);
}
/* Sprite sheet: 8-frame walk cycle */
.sprite {
animation: walk 0.8s steps(8) infinite;
}
Choosing the right easing
- UI transitions (hover, focus, state change):
ease-outorcubic-bezier(0, 0, 0.2, 1) - Elements entering the screen:
ease-out - Elements leaving the screen:
ease-in - Micro-interactions (toggles, checkboxes): spring
cubic-bezier - Scroll-driven animations:
linear(progress should match scroll position) - Loading/progress bars:
linear
* { transition-timing-function: cubic-bezier(0, 0, 0.2, 1); } gives every transition a natural deceleration.