CSS Effects April 2026 · 10 min read

CSS Animations & Keyframes: The Complete Guide

CSS animations bring interfaces to life — but done wrong, they slow pages down and frustrate users. This guide covers the full picture: from basic keyframe syntax to performance-safe techniques and accessibility requirements.

The Two CSS Animation Systems

CSS has two ways to animate: transitions (for state changes triggered externally) and animations (for self-playing sequences). Both have their place:

/* Transition: responds to class/state change */
.btn {
  background: #7c5cfc;
  transition: background 0.2s ease, transform 0.2s ease;
}
.btn:hover {
  background: #9333ea;
  transform: translateY(-2px);
}

/* Animation: runs automatically */
.spinner {
  animation: spin 1s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes Syntax

The @keyframes rule defines the sequence of styles at specific points in time. You can use percentages or the keywords from / to:

@keyframes fadeSlideIn {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Multi-step keyframes */
@keyframes pulse {
  0%   { transform: scale(1); opacity: 1; }
  50%  { transform: scale(1.05); opacity: 0.8; }
  100% { transform: scale(1); opacity: 1; }
}

The animation Shorthand

The animation property is shorthand for 8 individual properties:

.element {
  animation: name duration timing-function delay
             iteration-count direction fill-mode play-state;

  /* Example */
  animation: fadeSlideIn 0.4s ease-out 0.1s 1 normal forwards running;

  /* Most common — name, duration, easing */
  animation: fadeSlideIn 0.4s ease-out;

  /* Multiple animations */
  animation: fadeSlideIn 0.4s ease-out, pulse 2s ease-in-out 1s infinite;
}

Timing Functions Explained

The timing function controls the rate of change throughout the animation. Understanding the options is key to animations that feel natural:

.element {
  /* Built-in keywords */
  animation-timing-function: ease;        /* slow start, fast middle, slow end */
  animation-timing-function: ease-in;     /* slow start, accelerates */
  animation-timing-function: ease-out;    /* fast start, decelerates */
  animation-timing-function: ease-in-out; /* slow start and end */
  animation-timing-function: linear;      /* constant speed (good for rotations) */

  /* Custom cubic-bezier */
  animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring bounce */

  /* Steps (for sprite animations or typewriter effects) */
  animation-timing-function: steps(8, end);
}

For most UI animations, ease-out feels the most natural — objects that enter the screen decelerate as they arrive, like real physical objects. Reserve ease-in for elements leaving the screen.

fill-mode: What Happens Before and After

animation-fill-mode controls the element's style outside the animation's active duration:

.element {
  /* none (default): returns to original state after animation */
  animation-fill-mode: none;

  /* forwards: keeps final keyframe values after animation ends */
  animation-fill-mode: forwards;

  /* backwards: applies first keyframe values during delay period */
  animation-fill-mode: backwards;

  /* both: applies backwards + forwards */
  animation-fill-mode: both;
}

/* Most fade-in animations need "both" to avoid flash before start */
.fade-in {
  opacity: 0;
  animation: fadeIn 0.5s ease-out 0.2s both;
}
@keyframes fadeIn { to { opacity: 1; } }

Performance: Only Animate These Properties

CSS animations can trigger three different browser pipeline stages: layout, paint, and composite. Only composited properties are GPU-accelerated and smooth at 60fps:

/* ✅ GPU-composited: always smooth */
transform: translate(), scale(), rotate(), skew()
opacity

/* ⚠️ Triggers paint: avoid animating on many elements */
color, background-color, border-color, box-shadow

/* ❌ Triggers layout: never animate these */
width, height, padding, margin, top, left, font-size

/* Promote to GPU layer if needed */
.animated-element {
  will-change: transform; /* Use sparingly — right before animation */
}

Accessibility: Respecting Motion Preferences

Users with vestibular disorders or motion sensitivity can set their OS to "reduce motion". Always respect this preference:

/* Define animation normally */
.card {
  animation: slideIn 0.5s ease-out both;
}

/* Disable or reduce for users who prefer less motion */
@media (prefers-reduced-motion: reduce) {
  .card {
    animation: none; /* or a gentler alternative */
  }

  /* For transitions */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

The nuclear option (setting all durations to 0.01ms) works but may cause jarring instant state changes. A better approach is to define a simplified, opacity-only version of each animation for reduced-motion users rather than removing all animation entirely.

Common Animation Patterns

These are the animations that appear most frequently in production UI:

/* Fade in */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Slide up (enter from below) */
@keyframes slideUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Scale pop (for modals, tooltips) */
@keyframes scalePop {
  from { opacity: 0; transform: scale(0.92); }
  to   { opacity: 1; transform: scale(1); }
}

/* Shimmer (loading skeleton) */
@keyframes shimmer {
  from { background-position: -200% center; }
  to   { background-position: 200% center; }
}
.skeleton {
  background: linear-gradient(90deg, #1a1a2e 25%, #2a2a4e 50%, #1a1a2e 75%);
  background-size: 200% auto;
  animation: shimmer 1.5s linear infinite;
}

Common Mistakes

Related Guides

palette

Gradient Generator

Animated gradient backgrounds

layers

Glassmorphism Generator

Animated glass UI