Skip to content
Web animation enginesLesson 1 of 4Beginner15 min

CSS transitions & keyframes

The two native CSS animation primitives: transitions interpolate a state change, keyframes choreograph a named multi-step sequence. Learn when each one fits.

Kept on this device only.

In one line: a transition interpolates a single change between two states when something flips; a @keyframes animation choreographs a named, multi-step timeline you can replay on demand — reach for the first for hover and toggle states, the second for anything with more than one stop.

What it is

A transition watches a property and, when its value changes, fills in the in-between frames instead of snapping. You declare what can transition, how long it takes, and how it eases — then a state change (a hover, a class toggle, a React re-render) does the rest. There is no choreography; it is strictly A → B.

A @keyframes animation is a named timeline. You spell out the stops — 0%, 60%, 100% — and attach the timeline to an element with the animation shorthand. It runs on its own without a triggering state change, can loop, can hold its end state, and can describe motion a transition never could because it has more than two points.

The mental split is simple: a transition reacts to a change you already make; an animation is the change, written out as a sequence.

Why it matters

Both primitives are native. No library, no JavaScript in the hot path, nothing added to your bundle — the browser owns the timing loop. That alone makes them the correct default before you reach for an animation library.

They are also fast in the right way. When you animate transform and opacity, the work happens on the compositor thread: the browser hands the layer to the GPU and moves it without re-running layout or paint. That is why a CSS transform animation stays smooth even while the main thread is busy, and why "use transform, not top/left" is the single highest-leverage performance rule in web animation.

See it

Live demo
Tweak it2
Replay the move with a transition or a keyframe animation, and swap the timing function.

Hit Play and watch the box travel. Switch technique between transition and keyframes to feel the difference — the transition just interpolates the same toggle, while the keyframes run replays a named sequence each time. Change the timing function (try bounce) and read the generated CSS below the stage: every control maps to one line you could paste into a stylesheet.

How it works

A transition is four properties, usually written as a shorthand. transition-property names what to watch (transform), transition-duration sets the length (600ms), transition-timing-function sets the easing curve (ease, linear, or a cubic-bezier(...)), and transition-delay optionally holds it off. The transition only fires when the property's value actually changes.

An animation is a @keyframes block plus the animation shorthand. The block lists stops as percentages; the shorthand wires it up: animation: slide 600ms ease;. Add infinite to loop, alternate to ping-pong, or both to keep the final frame. Because nothing triggers it, the animation begins as soon as it is applied — in React you replay it by changing a key so the element remounts.

Build it

A transition for a hover lift, a small @keyframes pulse for attention, and a prefers-reduced-motion guard that flattens both — the three pieces almost every component needs.

CSS
CSS — a hover transition, a keyframes pulse, and a reduced-motion guard
/* Transition: one declaration, runs on any state change to transform. */
.card {
  transform: translateY(0);
  transition: transform 200ms ease, box-shadow 200ms ease;
}
.card:hover {
  transform: translateY(-4px); /* the browser interpolates the lift */
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}
 
/* Keyframes: a named, multi-step timeline you attach and replay. */
@keyframes pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}
.badge--new {
  animation: pulse 1200ms ease-in-out infinite;
}
 
/* Always honor the OS setting: drop motion to a single, instant frame. */
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }
  .badge--new {
    animation: none;
  }
}

Make it yours

Use the controls beside the demo above to change technique and timing function — each change updates the example live.

Experiment in the playground
  • Set technique to transition and toggle Play repeatedly — notice the box reverses on the same curve, because a transition runs in whichever direction the value changes.
  • Switch to keyframes and replay: the sequence always starts from 0%, because changing the React key remounts the element and restarts the timeline.
  • Compare ease against bounce — the bounce overshoots past the end point and settles back, motion a two-stop transition could imply but a keyframe sequence states explicitly.

Reproduce it with an LLM

Reproduce it with an LLM

You are a senior front-end engineer. Build the same effect — 'a card lifts and a chevron rotates 180° on hover/expand' — two ways in pure CSS: (1) a `transition` for the simple state change, and (2) a `@keyframes` animation for a small two-step attention pulse on the chevron. Use only compositor-friendly properties (transform, opacity), a custom-property-driven duration and timing function, a visible `:focus-visible` style, and a `@media (prefers-reduced-motion: reduce)` block that disables both. Add a one-line comment on when to prefer a transition vs a keyframe animation. Return only the HTML and CSS.

Pitfalls & accessibility

  • Don't animate display or rely on a transition to reveal something from display: none — there is no value to interpolate from, so the transition is skipped. Use opacity/visibility (or modern transition-behavior: allow-discrete) instead.

Further reading