In CSS, what can a @keyframes animation express that a transition cannot, and what starts each of them?
answer
- one reacts, one self-starts
- transition needs a value change
- percentages give you the middle
- loop, alternate, pause: animations only
- fill-mode has no transition equivalent
basics
~20 sA CSS transition interpolates between two states and only runs when a value actually changes, typically on :hover or a class toggle. A @keyframes animation declares many intermediate stops, starts on its own, and can loop, alternate direction, and be paused.
solid answer
~50 sBoth interpolate values over time, but they are triggered differently and can express different shapes of motion. A transition is reactive: you declare `transition: opacity 300ms ease`, and the browser interpolates only when the cascade gives that element a new value for `opacity` — a class toggle, a `:hover`, a media-query change. No change, no motion, and it always runs exactly once from the old value to the new one. An animation is declarative: the moment an element's computed `animation-name` points at a `@keyframes` rule, the animation starts by itself, with no interaction and no state change — which is why loading spinners and attention pulses are animations, not transitions. `@keyframes` also lets you declare intermediate stops at any percentage, so motion can overshoot, pause mid-way, or take a non-monotonic path. Only animations get `animation-iteration-count: infinite`, `animation-direction: alternate`, `animation-fill-mode`, and `animation-play-state: paused`.
code
css · 16 lines.button {
background-color: #1a4433;
transition: background-color 200ms ease-out;
}
.button:hover {
background-color: #2c6a52;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
.badge {
animation: pulse 1.2s ease-in-out infinite;
}go deeper
Be ready to say plainly that a transition only runs when a value changes, while a @keyframes animation starts on its own and can repeat. Naming the trigger for each is most of the answer.
Explain the mechanics: percentage stops, iteration-count, direction, fill-mode and play-state, and why a transition physically cannot express a mid-point value without chaining a second one.
Show judgment about which to reach for in a real UI — reversible interaction feedback versus self-starting or looping motion — and mention that an interrupted transition reverses smoothly while a restarted animation snaps to its schedule.
Own the house rule: where motion lives in the design system, which mechanism each category of motion uses, and how the team avoids two mechanisms driving one property, which is a class of bug that is very hard to see in review.
## Two mechanisms, one job CSS has exactly two built-in ways to change a value over time. A **transition** watches properties on an element and, when the cascade hands that element a *new* computed value for one of them, interpolates from the old value to the new one. A **keyframe animation** is a named script of states — a `@keyframes` rule — that the element plays from start to finish for as long as `animation-name` points at it. That difference in triggering is the whole story. A transition needs something to change. An animation needs nothing at all. ## What starts a transition Anything that changes the computed value of a transitioned property: a `:hover` or `:focus` state, a class added or removed, a media query flipping, a custom property being reassigned. The transition is defined on the element in its *resting* state so that it applies in both directions. ```css .button { background-color: #1a4; transition: background-color 200ms ease-out; } .button:hover { background-color: #2c6; } ``` Remove the `:hover` rule and nothing ever moves — the declaration is inert until a value changes. ## What starts an animation Nothing external. As soon as the element's computed `animation-name` resolves to a `@keyframes` rule, the animation begins (after `animation-delay`). It runs on page load, on insertion into the document, on the element becoming rendered. ```css @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } } .badge { animation: pulse 1.2s ease-in-out infinite; } ``` A transition cannot produce this. It has only two endpoints — the old value and the new one — so a fade that dips to 0.4 and comes back would require reacting to `transitionend` and setting a second value, which is a chain of transitions, not one. ## The keyframe selectors Inside `@keyframes`, stops are written as percentages of the total duration, and `from` / `to` are aliases for `0%` and `100%`. Several offsets can share one block: ```css @keyframes shake { 0%, 100% { margin-left: 0; } 25%, 75% { margin-left: -6px; } 50% { margin-left: 6px; } } ``` The percentages are positions in time, not in space, and they need not be listed in order — the browser sorts them. ## The properties that only animations have - `animation-name` — which `@keyframes` rule to play; `none` stops it. - `animation-duration` — how long one iteration takes. It defaults to `0s`, so an animation with no duration produces no visible motion. - `animation-timing-function` — the easing curve, per segment between keyframes. - `animation-delay` — how long to wait before starting. A *negative* delay starts the animation immediately, already partway through, which is the standard way to stagger many copies of one animation. - `animation-iteration-count` — a number, a fraction, or `infinite`. - `animation-direction` — `normal`, `reverse`, `alternate`, `alternate-reverse`. - `animation-fill-mode` — whether keyframe values apply before the start and after the end. - `animation-play-state` — `running` or `paused`. There is no CSS equivalent for a transition; you cannot pause one. All of these collapse into the `animation` shorthand, and multiple animations can be layered as a comma-separated list. ## During the run While an animation is running, its interpolated values apply above normal author declarations, so a plain rule setting the same property does not visibly win, however specific it is. When the animation ends with the default `animation-fill-mode: none`, the element snaps back to whatever the cascade says — which surprises people expecting the last keyframe to stick. ## Choosing between them Reach for a transition when there is a genuine before and after state and the user's action is the trigger: hovers, open/closed panels, focus rings, theme switches. Reach for `@keyframes` when the motion is self-starting (spinners, skeleton shimmers, entrance animations), when it repeats, or when it needs stops in the middle. When both would work — a one-shot fade-in on load, for instance — the animation is usually simpler, because a transition would need a value to change first, which means a second style write or a class added after mount.
- Could you fade an element in on page load using only a transition?Not with CSS alone in one pass. A transition needs the computed value to change after the element is already rendered with the old value, so you would have to add a class in a later frame from script. A `@keyframes` animation with `animation: fade-in 300ms` needs no trigger at all, which is why it is the normal answer for load-time motion.
- Both a transition and an animation target opacity on the same element at the same time. Which value shows?The transition. In the cascade, animated values apply above normal author declarations, and transitioned values apply above everything — including `!important` author rules. In practice this is a smell: two mechanisms driving one property means the animation's keyframe values will look ignored for as long as the transition is active.
- When would you prefer a transition even though an animation could do the job?When the motion is a direct response to an interaction that can reverse mid-flight. A hover transition interrupted halfway smoothly reverses from wherever it is, because the browser just interpolates from the current computed value to the new one. An animation restarted or reversed by swapping `animation-direction` jumps to the keyframe schedule instead, which reads as a glitch.
saying these in an interview costs you the question
- Claiming transitions can loop with an iteration count
- Thinking @keyframes needs a hover or class to start
- Saying transitions support intermediate percentage stops
- Assuming the last keyframe stays applied by default
- Treating animation and transition as interchangeable syntax