AniUI Academy

Transitions and Animations

The transition shorthand and why not every property can meaningfully transition, @keyframes and the animation shorthand for multi-step motion, and respecting prefers-reduced-motion.

9 min read

A page that only ever snaps instantly between states reads as mechanical. transition and @keyframes/animation are CSS's two tools for smoothing that out — one for simple state changes, one for a scripted, multi-step sequence.

transition: smoothing a property change

Without a transition, changing a CSS property (say, on :hover) happens instantly, in a single frame:

.button {
  background-color: #3b82f6;
}
.button:hover {
  background-color: #2563eb;
}

Add transition, and the browser animates smoothly between the old and new value instead of snapping:

.button {
  background-color: #3b82f6;
  transition: background-color 200ms ease-in-out;
}
.button:hover {
  background-color: #2563eb;
}

The shorthand's four parts, in order: property duration timing-function delay.

  • property — which property to animate (background-color, all for every animatable property, or a comma-separated list of several).
  • duration — how long the transition takes (200ms, 0.3s).
  • timing-function — the pacing curve: ease (default, starts and ends slowly), linear (constant speed), ease-in, ease-out, or a custom cubic-bezier(...).
  • delay (optional) — how long to wait before starting.

transition is declared on the base state, not the :hover state — it describes how any future change to that property on this element should be animated, in either direction (hovering on and hovering off both animate).

Not every property can meaningfully transition

A transition works by interpolating — computing in-between values along the path from the old value to the new one. background-color has interpolatable in-between values (a color partway between two colors is a real color). width does too (100px partway to 200px is 150px). But some properties have no such in-between state:

.modal {
  display: none;
  transition: display 200ms; /* has no effect — nothing to interpolate */
}
.modal.open {
  display: block;
}

display: none and display: block aren't two points on a continuous scale — an element is either laid out or it isn't, with nothing meaningfully "half-displayed" in between. transition: display is effectively a no-op. (A separate, more advanced technique exists for transitioning display itself using transition-behavior: allow-discrete, but that's beyond what this lesson needs — the practical takeaway is: reach for transitioning opacity and transform to fade or slide something in and out, not display, when you want an animated show/hide.)

@keyframes and the animation shorthand

transition only ever animates between two states — a start and an end. @keyframes describes a full sequence with as many steps as you want:

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

Each percentage is a point in the animation's timeline; the browser interpolates smoothly between consecutive keyframes, same as a transition does between two values, just with more waypoints along the way. from and to are shorthand for 0% and 100% when you only need two keyframes.

Apply a @keyframes sequence to an element with the animation shorthand:

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

The shorthand's parts, in order: name duration timing-function iteration-count direction fill-mode.

  • name — which @keyframes block to run (pulse here).
  • duration — how long one full pass through the keyframes takes.
  • timing-function — same pacing keywords as transition.
  • iteration-count — how many times to repeat: a number, or infinite.
  • direction (optional) — normal (default), reverse, or alternate (plays forward, then backward, then forward again — useful for a pulse that shouldn't visibly snap back to the start each loop).
  • fill-mode (optional) — whether the element keeps the first or last keyframe's styles before the animation starts or after it ends (forwards keeps the final keyframe's styles once the animation finishes, rather than snapping back to the element's un-animated state).

Respecting prefers-reduced-motion

Large or fast motion isn't just a stylistic taste — for some visitors, it causes real physical discomfort, including vestibular motion sickness triggered by parallax, zooming, or spinning effects. Most operating systems expose a setting for this ("Reduce Motion" on macOS/iOS, a similar option on Windows and Android), and CSS can detect it directly:

.badge {
  animation: pulse 1.5s ease-in-out infinite;
}
 
@media (prefers-reduced-motion: reduce) {
  .badge {
    animation: none;
  }
}

This is a real, standard, well-supported media query — the same shape as min-width/max-width, just checking an accessibility preference instead of a viewport size. When it matches reduce, the visitor has explicitly opted into wanting less motion, and the respectful response is to disable, shorten, or reduce the intensity of non-essential animation for them, not to force it on regardless.

Try it yourself

The badge below animates continuously with @keyframes, and the button demonstrates a simple transition. (Your browser's own reduced-motion setting isn't something this embed can detect, since it runs inside a sandboxed preview — in a real deployed page, wrap animation with the media query above so it's respected automatically.)

Try it yourself
Loading playground...

What to remember

  • transition: property duration timing-function delay; smooths a single change between two states — declare it on the base rule, not the changed state.
  • Not every property can transition meaningfully — display has no in-between value, so animating it directly is a no-op; animate opacity/transform for a show/hide effect instead.
  • @keyframes defines a multi-step sequence by percentage; animation: name duration timing-function iteration-count direction fill-mode; runs it.
  • @media (prefers-reduced-motion: reduce) detects a real OS-level accessibility setting — use it to disable or shorten animation for visitors who've asked for less motion.

Check yourself

3 questions · pass 3/3 to unlock Transforms

up to 50
  1. 1.Why can't display meaningfully transition between none and block?

  2. 2.What does the animation shorthand's iteration-count value control?

  3. 3.What does @media (prefers-reduced-motion:reduce) let you do?

3 left to answer