Articles

CSS Transitions vs Animations: Which to Use

CSS transitions animate a single state change; animations run independent, repeatable keyframe sequences. How to pick the right one for the job.

Takina Takina · · 4 min read
Close-up of CSS code on a screen

A CSS transition interpolates a property between two states in response to a change — hover, focus, a class toggle. A CSS animation runs a self-contained sequence of keyframes that can loop, reverse, and play without any triggering event at all. They share the same easing and timing machinery under the hood, but they solve different problems, and reaching for the wrong one is a common source of over-engineered CSS.

What a transition actually does

A transition watches a property for a change and smooths the jump between the old value and the new one. It needs a starting state, an ending state, and something that causes the change — usually a pseudo-class like :hover or :focus, or a class added by JavaScript.

.button {
  background: #2563eb;
  transition: background 200ms ease-out;
}

.button:hover {
  background: #1d4ed8;
}

Nothing happens until the state changes. There’s no keyframe list, no iteration count, no way to make it play automatically on page load without some external trigger (a class swap, an @starting-style rule, or JavaScript setting a property). Transitions are inherently two-state: from wherever the property is now, to wherever it’s going.

What an animation actually does

An animation is defined independently of any state change, as a named sequence of keyframes:

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

.badge {
  animation: pulse 1.5s ease-in-out infinite;
}

This runs the moment the element is rendered, with no triggering event required. It can loop forever (infinite), alternate direction each cycle (animation-direction: alternate), and define as many intermediate steps as needed. Multiple keyframes let you choreograph motion a transition can’t express — a bounce that overshoots, a spinner that never stops, a shake that reverses twice.

The core difference

TransitionsAnimations
TriggerRequires a state/property changeRuns on its own, no trigger needed
KeyframesTwo implicit states (start, end)Any number of explicit keyframes
LoopingNo native repeatanimation-iteration-count, including infinite
Direction controlOne-way (forward on change)Forward, reverse, alternate
Typical useHover, focus, toggled UI stateLoaders, decorative motion, looping effects
ComplexitySimpler, less markupMore setup (@keyframes block)

When to reach for a transition

Transitions are the right tool for almost anything that responds to user interaction: a button darkening on hover, a menu sliding open when a class is toggled, a card lifting on focus. They’re declarative, cheap to write, and don’t need a named keyframe block. If you’re just smoothing a before-and-after state, a transition is simpler and easier to reason about than an animation with two keyframes.

Transitions also pair well with CSS custom properties — toggling a custom property’s value and transitioning the properties derived from it is a common pattern for theming and state-driven UI.

When to reach for an animation

Reach for @keyframes when the motion needs to play without user input, loop, or pass through more than two states: a loading spinner, a skeleton-screen shimmer, an attention-grabbing pulse on a notification badge, or an entrance sequence that plays once on load. Anything that needs infinite repetition or a multi-step sequence belongs in an animation, because transitions have no concept of repeating or of intermediate keyframes.

The newer scroll-driven animations spec extends @keyframes further, letting scroll position (rather than time) drive the animation timeline — useful for progress indicators and parallax effects tied to scroll rather than the clock.

Performance: what actually matters

Both transitions and animations perform the same way for the same properties — the mechanism (interpolate values over time) is identical, only the triggering model differs. The real performance lever is which properties you animate, not which of the two features you use:

  • Cheap: transform and opacity. Both can run on the compositor thread, skipping layout and paint entirely on modern browsers.
  • Expensive: width, height, top, left, margin, and anything else that affects layout. Animating these forces the browser to recompute layout on every frame, which is where jank comes from. See the critical rendering path for why layout recalculation is the costly step.

Prefer transform: translateX() over animating left, and transform: scale() over animating width/height. This holds whether you’re using a two-line hover transition or a twelve-step keyframe animation — the rule is about the property, not the syntax.

Combining both

Real interfaces usually use both together. A modal might use a transition for its backdrop fade (a simple two-state opacity change on open/close) and a keyframe animation for a looping spinner shown while content loads inside it. Neither replaces the other; they cover different shapes of motion.

If you’re building responsive UI more broadly, clamp() and fluid typography (see CSS clamp and fluid typography) solve a related but separate problem — sizing rather than motion — and often show up in the same stylesheets as transitions and animations.

The takeaway

Use a transition when a property needs to smoothly change from one state to another in response to something — hover, focus, a toggled class. Use an animation when the motion needs to run independently, loop, reverse, or pass through more than two keyframes. Both are cheap on transform and opacity and expensive on layout-affecting properties, so the property you animate matters more for performance than which feature you reach for.

Takina Takina · · 4 min read

CSS object-fit and object-position, Explained

object-fit controls how an image or video is cropped inside its box, and object-position controls which part of it stays visible. How they work together.

#CSS #Web Development #Frontend
Takina Takina · · 5 min read

CSS inherit, initial, unset & revert Explained

CSS's four global keywords control where a property's value comes from. How inherit, initial, unset, and revert differ, with a comparison table.

#CSS #Web Development #Frontend
Takina Takina · · 4 min read

Dynamic Viewport Units: dvh, svh, and lvh Explained

dvh, svh, and lvh fix the classic mobile vh bug where browser toolbars cut off full-height layouts. Here's what each unit measures and when to use it.

#CSS #Web Development #Frontend