Understanding CSS Timing Functions

Master the art of animation timing to create fluid, engaging user interfaces with CSS timing functions, cubic-bezier curves, and performance best practices.

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 states
  • animation-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:

KeywordFeelCubic-BezierBest Use
linearConstant(0, 0, 1, 1)Loading spinners, progress bars
easeNatural(0.25, 0.1, 0.25, 1)General transitions (default)
ease-inAccelerating(0.42, 0, 1, 1)Elements leaving viewport
ease-outDecelerating(0, 0, 0.58, 1)Elements entering viewport
ease-in-outSmooth(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 start
  • jump-end: Last jump at animation end (default)
  • jump-none: Hold at both start and end
  • jump-both: Pause at both start and end
  • start: Same as jump-start
  • end: Same as jump-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 TypeRecommended Duration
Simple transitions150-300ms
Complex animations400-600ms
Loading indicatorsVariable

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:

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:

  1. Built-in presets (ease, linear, ease-in, ease-out, ease-in-out) cover common cases
  2. cubic-bezier() enables custom curves including elastic overshoot effects
  3. steps() creates discrete, frame-by-frame animations
  4. 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-motion for 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.

Ready to Build Smooth, Professional Animations?

Our web development team creates fluid, accessible animations that enhance user experience without sacrificing performance.