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.
Step-by-step resolution
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 forbackground-colorandbox-shadowon 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.
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: allinternally; override at the component boundary. - Transitioning
background-colorexplicitly still repaints; keep it to small elements. - Removing
allcan 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.
Related
- CSS Transitions vs Animations — the parent topic
- Debugging Transitions That Never Fire — the opposite problem
- Animating Colours Without Repaint — when colour transitions do need to stay