What Are CSS Timing Functions?
CSS timing functions determine how animations progress through their duration, controlling the "rhythm" and "feel" of motion on the web. The easing curve can make or break any animation on the web--understanding timing functions transforms mechanical transitions into fluid, engaging user experiences.
In the real world, there's little such thing as instant. Nothing just appears or disappears. Things move into or out of place, and our brains perceive changed states by observing that movement. Movement tells a story--the transition itself is the verb; the easing curve is the adverb.
Key timing function properties:
transition-timing-function- Controls easing between two statesanimation-timing-function- Controls easing within keyframe animations
Understanding how these timing functions work is essential for creating polished, professional interfaces that feel natural to users. When combined with CSS3 keyframe animations, you can create complex, multi-state animations that enhance user engagement without overwhelming the browser.
Transitions vs. Animations
CSS Transitions move elements between two states, triggered by state changes like hover or focus:
.btn {
background: lightblue;
transition: background 0.2s ease;
}
.btn:is(:hover, :focus) {
background: yellow;
}
CSS Animations move elements through multiple keyframe states, running automatically or on trigger:
.spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
Both use the same timing function concepts--transitions are simple A-to-B, animations can have multiple steps. For deeper exploration of CSS transitions and their full specification, see our dedicated guide to mastering transition effects.
Built-In Keyword Values
CSS provides five preset timing functions, each a shorthand for specific cubic-bezier values:
| Keyword | Feel | Cubic-Bezier | Best Use |
|---|---|---|---|
linear | Constant | (0, 0, 1, 1) | Loading spinners, progress bars |
ease | Natural | (0.25, 0.1, 0.25, 1) | General transitions (default) |
ease-in | Accelerating | (0.42, 0, 1, 1) | Elements leaving viewport |
ease-out | Decelerating | (0, 0, 0.58, 1) | Elements entering viewport |
ease-in-out | Smooth | (0.42, 0, 0.58, 1) | Modal dialogs, focused interactions |
/* Hover transition with natural easing */
.button {
transition: transform 0.3s ease;
}
/* Loading spinner with constant motion */
.spinner {
animation: spin 1s linear infinite;
}
The cubic-bezier() Function
The cubic-bezier() function creates custom timing curves beyond presets: cubic-bezier(x1, y1, x2, y2).
How It Works
- x values (time): Must be between 0 and 1--time cannot move backward
- y values (progress): Can exceed 0-1 for bounce/overshoot effects
- Control points shape the curve like vector design handles
/* Standard smooth easing */
.smooth {
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Elastic effect with overshoot */
.bouncy {
transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
Creating Elastic Effects
Negative y values create "pull back" effects; values above 1 create overshoot bounces:
/* Button bounce on click */
.bounce-button:active {
transform: scale(0.95);
transition: transform 0.1s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* Card slide with overshoot */
.slide-card {
transform: translateX(-100%);
transition: transform 0.5s cubic-bezier(0.68, -0.6, 0.32, 1.6);
}
Common Preset Curves
/* Material Design */
.material {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* iOS-like spring */
.ios-spring {
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
The linear() Function
The newer linear() function specifies multiple stop points: linear(0, 0.25, 0.5, 0.75, 1).
/* Custom multi-point easing */
.custom-ease {
animation: slide 2s linear(0, 0.25 25%, 0.9 75%, 1) forwards;
}
The steps() Function for Discrete Animations
The steps() function creates non-continuous animations: steps(n, position).
Step Position Values
jump-start: First jump at animation startjump-end: Last jump at animation end (default)jump-none: Hold at both start and endjump-both: Pause at both start and endstart: Same asjump-startend: Same asjump-end
/* Typewriter effect */
.typewriter {
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
animation: type 2s steps(20, end) forwards;
}
/* Sprite animation */
.sprite {
animation: walk 0.5s steps(6) infinite;
}
Performance Best Practices
For smooth 60fps animations, animate only GPU-accelerated properties:
Good (GPU-accelerated):
.smooth-animate {
transition: transform 0.3s ease, opacity 0.3s ease;
}
Avoid (triggers layout):
.bad-animate {
transition: width 0.3s ease, left 0.3s ease;
}
Using will-change Wisely
.will-change-optimized {
will-change: transform;
}
Use will-change sparingly--overuse creates unnecessary compositor layers and hurts performance. For more on optimizing your CSS for performance, explore our guide to CSS minification and optimization techniques.
Duration Guidelines
| Animation Type | Recommended Duration |
|---|---|
| Simple transitions | 150-300ms |
| Complex animations | 400-600ms |
| Loading indicators | Variable |
Make transitions as quick as possible without being so short users miss them. Around 200ms works for most transitions.
Accessibility: prefers-reduced-motion
Respect users who experience discomfort from motion:
.animated-element {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
.animated-element {
transition: transform 0.1s;
animation: none;
}
}
Note: With keyframe animations, timing functions apply to each step individually, not the entire animation.
Tools and Resources
Browser Dev Tools: All major browsers include easing editors--select an element with a cubic-bezier curve and click the curvy line icon to adjust visually.
Recommended Tools:
- cubic-bezier.com - Interactive curve visualizer
- easings.net - Visual cheat sheet with values
For a curated collection of CSS tools, check out our guide to 50 really useful CSS tools.
Common Animation Patterns
Hover Card with Bounce
.interactive-card {
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.interactive-card:hover {
transform: translateY(-5px);
}
Modal Entrance
.modal {
opacity: 0;
transform: scale(0.95);
animation: modalEnter 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes modalEnter {
to { opacity: 1; transform: scale(1); }
}
Staggered List
.list-item {
opacity: 0;
transform: translateY(20px);
animation: listEnter 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
Summary
CSS timing functions are essential for polished animations:
- Built-in presets (ease, linear, ease-in, ease-out, ease-in-out) cover common cases
- cubic-bezier() enables custom curves including elastic overshoot effects
- steps() creates discrete, frame-by-frame animations
- linear() allows multi-point easing profiles
Remember:
- Match timing to purpose (ease-out for entering, ease-in for leaving)
- Keep durations short (150-300ms for transitions)
- Animate only transform and opacity for performance
- Respect
prefers-reduced-motionfor accessibility - Test in context--what looks good alone may feel wrong in place
Mastering these timing functions is a key skill in professional web development, enabling you to create interfaces that feel polished, responsive, and delightful to use.
Frequently Asked Questions
What's the difference between ease and cubic-bezier(0.25, 0.1, 0.25, 1)?
They're identical! The ease keyword is simply a shorthand for that specific cubic-bezier value. Use ease for simplicity or cubic-bezier when you need to reference or modify the value.
Can I use negative values in cubic-bezier()?
Only for y coordinates. x values must be between 0 and 1 because time cannot move backward. Negative y values create pull-back effects; y values above 1 create overshoot bounces.
Why is my animation janky despite using cubic-bezier()?
The easing function isn't the issue--likely the properties you're animating. Avoid animating layout properties like width, height, margin. Stick to transform and opacity for smooth 60fps animations.
How do I create a bounce effect without JavaScript?
Use cubic-bezier() with y values exceeding 1. For example, cubic-bezier(0.68, -0.55, 0.265, 1.55) creates a noticeable bounce. The second control point's y value (1.55) causes the overshoot.
Should I use steps() or keyframes for sprite animations?
steps() is more efficient and semantic for sprite animations. It creates discrete jumps between frames without defining multiple keyframe percentages. Use steps(n) where n equals your number of frames.