What is the difference between transition and animation?

Beginner10 min interview
Skills tested:
Explaining that a transition requires a state change and an animation does notKnowing which one supports intermediate keyframes and loopingChoosing correctly for a hover effect, a loading spinner, and an entranceNaming the transition and animation sub-propertiesKnowing that both should respect prefers-reduced-motion

Advertisement

🧩 Scenario

The choice is usually obvious once framed correctly: a hover colour change is a transition because there are two states and the browser interpolates between them, while a loading spinner is an animation because there is no state change to react to and it must repeat forever. Getting it wrong produces either an animation with a pile of JavaScript to trigger it, or a transition being toggled by a class in a way that cannot express a midpoint.

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

Aspecttransitionanimation
Needs a triggerYes, a value changeNo, runs on apply
Runs on initial renderNo, needs @starting-styleYes
Intermediate stepsNo, two endpoints onlyYes, any number of keyframes
LoopingNoYes, animation-iteration-count
Reversible on interruptNaturallySnaps back unless handled
Pause and resumeNoYes, animation-play-state
Fill behaviourN/Aanimation-fill-mode
Best forHover, focus, toggles, accordionsSpinners, 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-style addresses 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-motion without being prompted

Follow-Up Questions

  1. What does animation-fill-mode: forwards do, and how does it differ from both?
  2. How does @starting-style work, and what problem does transition-behavior: allow-discrete solve alongside it?
  3. Why does a negative animation-delay start the animation mid-sequence rather than delaying it?
  4. What are transitionend and animationend, and why can a transition fire neither if the value never changes?
  5. 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-motion guard, or writing that guard as animation: none in a way that stops animationend events 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.

Frequently Asked Questions

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