Why transition: all Is a Performance Trap

Part of CSS Transitions vs Animations in Core CSS Animation Fundamentals.

The problem

A button component has transition: all 200ms ease. It works: hover lifts the button and changes its colour. Months later, a dark-mode toggle takes a full second to finish and the page stutters during it. A responsive layout change makes every card slowly resize instead of snapping. A focus ring appears 200ms after the focus moves, and keyboard users complain the page feels laggy. Nobody connects any of these to the button’s one-line transition.

all is convenient because it animates whatever changes. That is also exactly what makes it dangerous.

Root cause analysis: all means every property that changes, for any reason

It animates layout properties. When a container query, media query or content change alters width, padding, margin, height or font-size on an element with transition: all, the browser interpolates those too. Each frame of that interpolation runs layout — for the element and everything it pushes — which is the most expensive kind of animation, described in the compositing tier reference.

It animates paint properties on theme changes. Switching theme changes color, background-color, border-color, box-shadow and fill across hundreds of elements at once. With all, every one of them repaints every frame for the transition’s duration. A theme switch that could be one repaint becomes sixty.

It catches inherited and derived changes. A parent’s font-size change alters children’s computed line-height and em-based padding; children with transition: all animate those as well. The element being animated was never touched by your state change.

It delays feedback that should be instant. Focus outlines, visibility flips, cursor-relevant properties and colour contrast changes for error states all get delayed. outline appearing slowly is an accessibility problem, not a style one.

It is harder to reason about for reduced motion. An override has to cancel “everything”, and later rules that add a specific transition may accidentally re-enable all.

Main-thread work during a dark-mode togglePage with 320 styled components; transition duration 300 ms.Main-thread work during a dark-mode toggleInstant theme change, no transitions38 ms totalExplicit transitions on buttons only64 ms totaltransition: all on every component1210 ms total
Page with 320 styled components; transition duration 300 ms.

Step-by-step resolution

Replacing all with intentWrite down what should move; everything else should snap.Replacing all with intent1Search for transition: all and transition: with a duration but no property name.The shorthand without a property also means all2For each rule, list the properties its states actually change on purpose.Usually transform, opacity and one colour3Replace with an explicit list, each with its own duration if needed.Nothing unintended animates4Toggle theme, resize across breakpoints and change font size.Confirms these now snap5Record an interaction and check for Layout events during transitions.Only intended properties interpolate6Mirror the property list in the reduced-motion override.
Write down what should move; everything else should snap.

Production code pattern

/* Before: every property change on this button animates, forever. */
.button-old {
  transition: all 200ms ease;
}

/* After: only the properties the design intends to move. */
.button {
  transition:
    translate 160ms cubic-bezier(0.2, 0.8, 0.2, 1),
    background-color 160ms ease-out,
    box-shadow 160ms ease-out;
}
.button:hover {
  translate: 0 -1px;
  background-color: var(--color-primary-700);
}
.button:focus-visible {
  outline: 2px solid var(--color-primary-600);   /* not in the list: appears instantly */
  outline-offset: 2px;
}

/* Theme tokens change instantly; no component inherits a colour transition by accident. */
:root[data-theme="dark"] {
  --color-surface: #16131f;
  --color-text-body: #ece9f5;
}

@media (prefers-reduced-motion: reduce) {
  .button {
    transition:
      background-color 100ms linear,
      box-shadow 100ms linear;                   /* colour feedback stays, movement goes */
  }
  .button:hover { translate: none; }
}

Rendering Impact: composite for translate, paint for background-color and box-shadow on a small element. Everything outside the list — layout properties, theme colours elsewhere, the focus outline — changes in a single frame, as it should.

An explicit list also documents the design. Someone reading transition: translate, background-color, box-shadow knows what the hover does without finding the :hover rule; someone reading transition: all knows nothing.

What transition: all animates that you probably did not meanEach row is a real regression traced back to the shorthand.What transition: all animates that you probably did not meanTriggerCostUser-visible effectwidth, padding,marginContainer or media queryLayout per frameCards slide into new sizescolor,background,borderTheme switchPaint per frame, page-wideSlow, janky theme changeoutlineKeyboard focusPaintFocus ring lags behind focusfont-size,line-heightUser zoom or parent changeLayout per frameText reflows slowlyvisibilityShow and hideStyleHidden content still clickable
Each row is a real regression traced back to the shorthand.

Linting it away

Once removed, keep it out. Stylelint’s declaration-property-value-disallowed-list can reject transition: all and transition-property: all; a custom rule can also reject a transition shorthand without a property name, which defaults to all. Enforcing this alongside motion tokens is covered in enforcing motion tokens with Stylelint.

Verification checklist

Constraints and trade-offs

  • Explicit lists need updating when a design adds a new animated property.
  • Some component libraries set transition: all internally; override at the component boundary.
  • Transitioning background-color explicitly still repaints; keep it to small elements.
  • Removing all can expose places that relied on accidental smoothing of layout changes; decide deliberately whether they should animate.
  • A lint rule catches authored CSS but not inline styles injected by script.

Frequently asked questions

Is transition: all always bad?

It is rarely what you mean. It animates every changed property, including layout and theme colours, for as long as the rule exists. Explicit property lists are safer and self-documenting.

Does transition: 200ms without a property mean all?

Yes. transition-property defaults to all, so a shorthand with only a duration transitions every property.

Why is my dark mode toggle slow?

Components with transition: all or colour transitions animate every colour change the theme causes, repainting large areas each frame. Remove those transitions or scope them to interactive states.

Does transition: all cause layout thrashing?

It causes layout animations whenever a layout property changes, which can run layout every frame. Thrashing specifically means interleaved reads and writes, but the frame cost is similar.