CSSCSS Animations Overview

CSS Animations Overview

CSS animations let an element move, change, or cycle through styles automatically, without waiting for anything to happen. Where a transition only plays when a property actually changes value — usually triggered by :hover, :focus, or a class toggle from JavaScript — an animation defines its own timeline of steps and runs it on its own, on page load, in a loop, forever if you want. A spinning loading indicator or a pulsing notification badge are animations, not transitions, because nothing external is "triggering" a state change.

Transitions vs. animations

Transition

Animation

Trigger

Needs a state change (:hover, class toggle, JS)

Runs automatically once applied — no trigger needed

Steps

Only two states: start and end

Any number of steps via @keyframes (0%, 25%, 50%...)

Looping

Plays once per trigger

Can repeat infinitely with animation-iteration-count

Direction control

Reverses automatically if the trigger reverses

Explicit control via animation-direction (normal, reverse, alternate)

Typical use

Hover states, focus rings, menu open/close

Loaders, attention-grabbers, ambient motion, onboarding

Two-part structure

Every CSS animation has two halves that work together:

  • @keyframes — defines what happens: a named sequence of styles at points along the timeline (0%, 50%, 100%, or from/to).

  • animation- properties* — define how it plays on a given element: which keyframes to use, how long, how many times, what easing.

A simple worked example

A notification dot that pulses continuously, drawing the eye without any interaction:

CSS
@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  70% {
    transform: scale(1.6);
    opacity: 0.4;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.notification-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #e74c3c;

  animation-name: pulse;
  animation-duration: 1.6s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;

  /* shorthand equivalent of the four lines above: */
  /* animation: pulse 1.6s ease-in-out infinite; */
}

Nothing on the page needs to be hovered or clicked — the moment this element renders, animation-name links it to the pulse keyframes and the browser starts cycling through them on its own, forever, because of infinite.

A second example — a bouncing loader

CSS
@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-16px);
  }
}

.loader-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #0066cc;
  animation: bounce 0.6s ease-in-out infinite;
}

/* Stagger three dots so they bounce in sequence */
.loader-dot:nth-child(2) { animation-delay: 0.15s; }
.loader-dot:nth-child(3) { animation-delay: 0.3s; }

This combines an animation with animation-delay and :nth-child() to create the classic three-dot "typing" loader — each dot runs the exact same keyframes, just offset in time.

Next
Explore each animation property in depth — [animation-name & duration](/css/animation-name), [animation-timing-function & delay](/css/animation-timing), [animation-iteration-count & direction](/css/animation-iteration), [animation-play-state](/css/animation-play-state), [animation-fill-mode](/css/animation-fill-mode), and the [animation shorthand](/css/animation-shorthand).