CSS Animations: The Complete Guide

CSS animations let you create multi-step motion sequences that run on page load, loop infinitely, or trigger on demand — all without JavaScript. This guide covers @keyframes, every animation property, timing functions, and gives you 7 copy-paste recipes. Build them visually? Use the free animation builder →

How CSS animations work

CSS animations have two parts: a @keyframes rule that defines the animation steps, and the animation property that applies it to an element.

/* 1. Define the keyframes */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* 2. Apply to an element */
.card {
  animation: fadeIn 0.5s ease-out forwards;
}

The animation shorthand

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

/* Example */
animation: slideIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.2s 1 normal forwards running;

/* Multiple animations */
animation: fadeIn 0.5s ease-out, slideUp 0.6s ease-out 0.1s;

Individual properties

animation-name: fadeIn;
animation-duration: 0.5s;
animation-timing-function: ease-out;    /* ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() */
animation-delay: 0.2s;                  /* negative delays start mid-animation */
animation-iteration-count: 1;           /* number | infinite */
animation-direction: normal;            /* normal | reverse | alternate | alternate-reverse */
animation-fill-mode: forwards;          /* none | forwards | backwards | both */
animation-play-state: running;          /* running | paused */

Live demo

A pulsing box using @keyframes with scale and opacity.

Copy-paste animation recipes

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

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

/* Scale in (pop) */
@keyframes scaleIn {
  from { opacity: 0; transform: scale(0.9); }
  to   { opacity: 1; transform: scale(1); }
}

/* Bounce */
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-20px); }
}

/* Spin */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Pulse */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }
}

/* Shake (error feedback) */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-8px); }
  50%      { transform: translateX(8px); }
  75%      { transform: translateX(-4px); }
}

Timing functions deep dive

The animation-timing-function controls the acceleration curve:

For UI animations, cubic-bezier(0.16, 1, 0.3, 1) (a snappy ease-out) feels more polished than the built-in ease-out. It's the curve used by most modern design systems.

Animations vs transitions

Respecting reduced motion

Some users are sensitive to motion. Always provide a reduced-motion fallback:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

CSS animations in Tailwind

<!-- Built-in animations -->
<div class="animate-spin">Loading</div>
<div class="animate-bounce">Scroll down</div>
<div class="animate-pulse">Loading skeleton</div>
<div class="animate-ping">Notification dot</div>

<!-- Custom in tailwind.config.js -->
animation: {
  'fade-in': 'fadeIn 0.5s ease-out forwards',
}
keyframes: {
  fadeIn: { from: { opacity: '0' }, to: { opacity: '1' } },
}

Performance tips

Build animations visually

Pick your timing function, set keyframes, preview the animation live — copy the full @keyframes CSS in one click.

Open the Animation Builder →

FAQ

What is the difference between CSS transitions and CSS animations?

Transitions animate between two states and require a trigger (like :hover or a class toggle). Animations use @keyframes to define multiple steps, can run on page load, loop infinitely, and do not need a trigger.

How do I make a CSS animation loop forever?

Set animation-iteration-count: infinite. For example: animation: spin 1s linear infinite.

What does animation-fill-mode: forwards do?

It tells the element to keep the styles from the last keyframe after the animation ends, instead of snapping back to its original state.

How do I respect users who prefer reduced motion?

Use the @media (prefers-reduced-motion: reduce) media query to set animation-duration to near zero and limit iteration count to 1. This respects the user's OS-level motion preference.

Related tools