Lesson 29 of 32
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.
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,allfor 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 customcubic-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@keyframesblock to run (pulsehere).duration— how long one full pass through the keyframes takes.timing-function— same pacing keywords astransition.iteration-count— how many times to repeat: a number, orinfinite.direction(optional) —normal(default),reverse, oralternate(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 (forwardskeeps 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.)
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 —
displayhas no in-between value, so animating it directly is a no-op; animateopacity/transformfor a show/hide effect instead. @keyframesdefines 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
1.Why can't display meaningfully transition between none and block?
2.What does the animation shorthand's iteration-count value control?
3.What does @media (prefers-reduced-motion:reduce) let you do?
3 left to answer