Back to Performance
Performance
medium
mid

Which CSS properties should you avoid animating, and why prefer transform and opacity?

Avoid animating layout properties (width, height, top, left, margin, padding) — they trigger reflow + paint every frame on the main thread. Avoid paint-heavy ones (box-shadow, background, border-radius) when possible. Prefer `transform` and `opacity`: they're compositor-only, GPU-accelerated, and run off the main thread, so they stay at 60fps.

5 min read·~8 min to think through

Not all CSS properties are equal to animate. The cost depends on which stage of the rendering pipeline they trigger: Style → Layout → Paint → Composite.

Avoid: layout-triggering properties

width, height, top, right, bottom, left, margin, padding, border-width, font-size...

Animating these forces Layout (reflow) every frame — the browser recalculates geometry, often for siblings and children too — then Paint, then Composite. It all runs on the main thread, so it competes with your JS and easily misses the ~16ms frame budget → jank.

Avoid where possible: paint-heavy properties

color, background-color, box-shadow, border-radius, background-image...

These skip Layout but still force Paint every frame. Cheaper than layout, but still not free. A common trick: instead of animating box-shadow, put the shadow on a pseudo-element and animate its opacity.

Prefer: transform and opacity

transform (translate, scale, rotate, skew) and opacity are compositor-only:

  • No Layout, no Paint.
  • The element gets its own GPU layer; the compositor just re-positions/re-blends it.
  • Can run on the compositor thread, off the main thread — smooth even while JS is busy.
css
/* ❌ avoid */
.menu { transition: left 300ms, height 300ms; }

/* ✅ prefer */
.menu { transition: transform 300ms, opacity 300ms; }
Want to animate…Don'tDo
Move somethingtop / left / margintransform: translate()
Resize somethingwidth / heighttransform: scale()
Show / hidedisplay / visibilityopacity (+ visibility at the end)
Reorder / shift listanimate topthe FLIP technique with transform

When you genuinely must animate layout

  • Use the FLIP technique: measure First and Last positions, apply an inverting transform, then animate the transform to zero — you get a layout result with a compositor animation.
  • Animate only on a small, isolated element.
  • Consider content-visibility / contain to limit layout scope.

will-change — the careful tool

will-change: transform pre-promotes an element to its own layer so the first frame isn't janky. But each layer costs GPU memory — use it on the few elements that animate, remove it when done. It's a scalpel, not a default.

Senior framing

The senior framing: "cheap to animate" = "doesn't reach Layout or Paint." transform/opacity are the safe set; everything else costs main-thread work per frame. Knowing the FLIP pattern for when you do need a layout change, and that will-change has a memory cost, separates a real answer from "just use transform."

Follow-up questions

  • What is the FLIP animation technique?
  • How would you animate a box-shadow performantly?
  • Why is animating `display` impossible/janky?

Common mistakes

  • Animating width/height/top/left and blaming the device for jank.
  • Animating box-shadow directly instead of an opacity proxy.
  • Trying to transition `display: none` — it's not animatable.
  • Leaving will-change on permanently.

Edge cases

  • `visibility` IS transitionable in a stepwise way; `display` is not (until newer @starting-style support).
  • Transform-based scale can distort children unless you counter-scale.
  • Sub-pixel transforms can cause text blur on some GPUs.

Real-world examples

  • Drawer/menu slide-ins, modal fades, list reordering with FLIP, hover effects.

Related questions