CSS animations let an element move through a whole sequence of styles over time, automatically, without needing a trigger like a hover or a click. Where a transition only interpolates between two states (a "before" and an "after"), an animation defined with @keyframes can define any number of intermediate steps, and can loop indefinitely — making it the right tool for things like a spinning loader, a pulsing notification badge, or an entrance effect that plays as soon as the page loads.

Defining @keyframes

A @keyframes rule names an animation and describes what it should look like at different points along its timeline, using percentages from 0% (the start) to 100% (the end) — or the shorthand keywords from and to for a simple two-step animation.

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.1);
    opacity: 0.7;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

This defines three checkpoints: the element starts at its normal size, grows 10% and fades slightly at the halfway point, then returns to normal by the end — producing a gentle "breathing" pulse effect when played.

The animation Shorthand

Defining @keyframes alone does nothing — you still need to apply it to an element with the animation property, which (like transition) is a shorthand bundling several sub-properties together.

<span class="notification-dot"></span>
.notification-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  background: #ef4444;
  border-radius: 50%;
  animation: pulse 1.5s ease-in-out infinite;
}

This plays the pulse animation defined above on a small red dot, taking 1.5 seconds per cycle, easing smoothly in and out, and repeating infinitely — so the dot continuously grows and shrinks to draw attention, with no JavaScript and no trigger needed. The shorthand here reads as: name, duration, timing-function, iteration-count — though it also accepts animation-delay, animation-direction (e.g. alternate, to reverse every other cycle), and animation-fill-mode (to control what styles apply before the animation starts or after it ends) if you need finer control.

Transitions vs Animations

The dividing line is simple once you see it side by side: a transition needs something to change (usually a class toggling, like :hover) and only ever moves between two states. An animation plays on its own, can define as many intermediate steps as you want via @keyframes, and can repeat forever without any external trigger at all.

Both ultimately animate the same kinds of properties under the hood — transform, opacity, background-color, and so on — so the choice usually comes down to whether the effect is triggered by a state change (use a transition) or needs to run automatically and possibly loop (use a keyframe animation). A spinning loading indicator, for instance, has no "before" and "after" state to transition between — it just needs to keep rotating — which makes it a textbook @keyframes use case: @keyframes spin { to { transform: rotate(360deg); } } applied with animation: spin 1s linear infinite;.