What is the difference between transition and animation?
Advertisement
🧩 Scenario
Architecture Walkthrough
A Transition Reacts to a State Change
transition interpolates a property between its old and new computed values when something causes that value to change. The trigger is external: a :hover, a :focus, a class added by JavaScript, an attribute change, or a media query becoming true.
This has a hard consequence: a transition needs two computed values, a from and a to. On first render there is no previous value, so a transition cannot animate an element's initial appearance. It also means transitioning to or from display: none historically did nothing, because the element is not rendered in one of the two states. The newer @starting-style rule and the transition-behavior: allow-discrete value address both cases, but the underlying model still requires a change to react to.
Transitions are inherently reversible and interruptible. When a hover ends, the transition runs back from wherever it currently is, which produces natural-feeling interactions with no extra work. This is their main advantage and the reason they are correct for the majority of interaction feedback.
They are also limited to two endpoints. There is no way to express "move right, then down" with a transition, because there is no vocabulary for an intermediate state.
An Animation Plays Keyframes
animation runs a @keyframes sequence and needs no trigger at all: it starts as soon as it is applied, which is usually on render. That is what makes it the right tool for anything that must run without user input, such as a loading spinner, a skeleton shimmer, or an entrance effect.
Keyframes give it capabilities transitions do not have. Any number of intermediate steps can be defined with percentages, so multi-stage motion is straightforward. animation-iteration-count can loop, including infinite. animation-direction can alternate or reverse. animation-fill-mode controls whether the first and last keyframe values persist before and after the run. animation-play-state can pause and resume. And animation-delay accepts negative values to start mid-sequence, which is how staggered lists are built without JavaScript.
The cost is that animations are less naturally interruptible. Removing an animation snaps the element back to its base state rather than easing out, which is why hover effects built as animations often feel abrupt on mouse-out.
The Sub-Properties
transition is shorthand for transition-property, transition-duration, transition-timing-function, transition-delay, and transition-behavior.
animation is shorthand for animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode, and animation-play-state. Modern CSS adds animation-timeline and animation-range for scroll-driven animation.
Worth noting on both: transition: all is a common shortcut and a bad default. It transitions every animatable property, including ones that trigger layout, and it makes future changes accidentally animated. Naming the properties is both faster and more predictable.
Choosing
Use a transition when there is a state change and two endpoints: hover, focus, active, checked, a toggled class, an accordion open state. It is shorter, reversible, and interruptible.
Use an animation when there is no state change, when the motion has more than two stages, when it must loop, or when it must run on load: spinners, skeletons, entrance effects, attention pulses, staggered reveals.
Both Must Respect Reduced Motion
prefers-reduced-motion: reduce is a user setting, often enabled because motion causes nausea or dizziness for people with vestibular disorders. Honouring it is not optional polish.
The standard approach is a global guard that reduces durations to effectively zero rather than removing the rules, which keeps any completion events firing and avoids breaking JavaScript that waits on transitionend. Some motion, such as a subtle opacity fade, is generally considered acceptable under reduced motion, while large translations, parallax, and scaling are the problematic kinds.
Key Code Explained
/* TRANSITION: needs a state change; two endpoints; reversible */
.button {
background: #3b82f6;
transform: translateY(0);
transition: background 200ms ease, transform 200ms ease;
}
.button:hover {
background: #2563eb;
transform: translateY(-2px);
}
/* On mouse-out it eases back from wherever it is. No extra CSS needed. */
/* transition: all is a bad default: it animates layout properties
and silently animates anything you add later */
.avoid {
transition: all 200ms ease;
}
/* ANIMATION: no trigger needed, loops, multiple stages */
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 1s linear infinite;
}
/* Multi-stage motion a transition cannot express */
@keyframes toss {
0% { transform: translate(0, 0); opacity: 1; }
50% { transform: translate(100px, 0); opacity: 1; }
80% { transform: translate(100px, 60px); opacity: 1; }
100% { transform: translate(100px, 60px); opacity: 0; }
}
.token {
animation: toss 900ms ease-in-out forwards;
}
/* Entrance on load: a transition cannot do this — there is no prior value */
@keyframes fade-up {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fade-up 300ms ease-out both;
}
/* Negative delay starts mid-sequence: staggering with no JavaScript */
.dot:nth-child(2) { animation-delay: -0.8s; }
.dot:nth-child(3) { animation-delay: -0.6s; }
/* Modern additions that close the transition gaps */
.popover {
transition: opacity 200ms, display 200ms allow-discrete;
}
@starting-style {
.popover {
opacity: 0; /* gives the transition a "from" value on first render */
}
}
/* REDUCED MOTION: shorten rather than remove, so events still fire */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
The .button rule is the case for transitions in three lines. Both the hover-in and the hover-out are handled, the reversal starts from wherever the current value happens to be if the user moves quickly, and there is no keyframe block or trigger logic. Rebuilding it as an animation would need two keyframe sets and would still snap on mouse-out.
The .token keyframes are the case for animations. Three-stage motion with a fade at the end has no transition equivalent, because there is no syntax for an intermediate state between two endpoints.
The reduced-motion block is worth writing as shown rather than with animation: none. Setting a near-zero duration keeps transitionend and animationend firing, so any JavaScript sequencing on those events continues to work for users who have the setting enabled.
Tradeoffs
| Aspect | transition | animation |
|---|---|---|
| Needs a trigger | Yes, a value change | No, runs on apply |
| Runs on initial render | No, needs @starting-style | Yes |
| Intermediate steps | No, two endpoints only | Yes, any number of keyframes |
| Looping | No | Yes, animation-iteration-count |
| Reversible on interrupt | Naturally | Snaps back unless handled |
| Pause and resume | No | Yes, animation-play-state |
| Fill behaviour | N/A | animation-fill-mode |
| Best for | Hover, focus, toggles, accordions | Spinners, skeletons, entrances, staggers |
What Interviewers Actually Check
- Whether you state that a transition needs a state change and an animation does not
- Whether you know a transition cannot animate initial render, and ideally that
@starting-styleaddresses it - Whether you know only animations support midpoints, looping, and pausing
- Whether you can pick correctly for a hover, a spinner, and an entrance
- Whether you mention
prefers-reduced-motionwithout being prompted
Follow-Up Questions
- What does
animation-fill-mode: forwardsdo, and how does it differ fromboth? - How does
@starting-stylework, and what problem doestransition-behavior: allow-discretesolve alongside it? - Why does a negative
animation-delaystart the animation mid-sequence rather than delaying it? - What are
transitionendandanimationend, and why can a transition fire neither if the value never changes? - How do the Web Animations API and scroll-driven animations relate to what CSS transitions and animations can express?
Common Candidate Mistakes
- Expecting a transition to animate an element's first appearance, when there is no previous computed value to interpolate from
- Building a simple hover effect as an animation, which requires two keyframe sets and still snaps abruptly when the pointer leaves
- Trying to express multi-stage or looping motion as a transition, which has no vocabulary for an intermediate state or a repeat count
- Using
transition: all, which animates layout-affecting properties and silently animates any property added to the rule later - Shipping motion with no
prefers-reduced-motionguard, or writing that guard asanimation: nonein a way that stopsanimationendevents other code depends on
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you state the trigger requirement for each mechanism?
- Can you name the sub-properties of both shorthands?
- Can you explain why transitions cannot loop or express a midpoint?
- Can you pick correctly for a hover effect, a spinner, an entrance, and a staggered reveal?
- Can you write the reduced-motion guard and explain why near-zero duration beats removal?
Summary
A transition interpolates a property between its old and new computed values in response to a state change: a hover, a focus, a toggled class, or a media query flipping. Because it needs both a from and a to value, it cannot animate an element's initial render, and it has no vocabulary for an intermediate state, so it is limited to two endpoints. In exchange it is naturally reversible and interruptible, which is why it is the correct tool for the large majority of interaction feedback.
An animation plays a @keyframes sequence and needs no trigger, starting as soon as it is applied. Keyframes bring capabilities transitions lack: any number of intermediate steps, looping via animation-iteration-count, alternating direction, fill behaviour before and after the run, pausing with animation-play-state, and negative delays that start mid-sequence for staggered effects. The tradeoff is that removing an animation snaps rather than eases, so hover effects built as animations feel abrupt.
Choose a transition when there is a state change with two endpoints, and an animation when there is no trigger, more than two stages, a loop, or a need to run on load. Modern CSS narrows the gap with @starting-style and transition-behavior: allow-discrete, which let transitions handle entrances and discrete properties like display. Whichever you use, honour prefers-reduced-motion, and prefer shortening durations to near zero over removing the declarations so that transitionend and animationend listeners keep working.
Can a transition run on page load without any interaction?
Not reliably. A transition needs a change from one computed value to another, and there is no previous value on first render. Use an animation, or the newer @starting-style rule, for entrance motion.
Which one can loop?
Only animation. animation-iteration-count: infinite loops; a transition runs once per state change and has no repeat mechanism.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement