CSS animation performance: what to animate

Why transform and opacity run at 60fps while width and top stutter — and how to fix animations that jank.

Guide · 7 min read · Updated 19 July 2026

The rendering pipeline, briefly

Understanding animation performance requires knowing what the browser does each frame. There are three stages: layout (calculating where every element goes), paint (filling in pixels — colours, text, shadows), and composite (assembling the painted layers into the final image).

The cost difference between them is enormous. Changing a property that affects layout forces the browser to redo all three stages, for potentially the entire page. Changing a composite-only property skips straight to the last stage — and that stage runs on the GPU, off the main thread.

At 60fps the browser has about 16.7 milliseconds per frame for everything. Layout on a complex page can consume that alone, which is why the property you choose matters more than any other optimisation.

The cheap properties: transform and opacity

Only two properties reliably skip layout and paint entirely: transform and opacity. Everything you want visually is usually achievable through them.

Instead of animating width and height, use transform: scale(). Instead of top and left, use transform: translate(). Instead of toggling display, transition opacity — paired with visibility so the hidden element leaves the tab order, as covered in our menu animations.

This is why every effect in the animation library animates transform or opacity: they're the two the compositor can handle alone.

The expensive ones

Layout-triggering properties are the worst: width, height, top, left, margin, padding, font-size. Each frame forces a full recalculation, and because layout can cascade, changing one element's size can reflow its siblings and ancestors too.

Paint-triggering properties are cheaper but not free: background-color, box-shadow, border-radius, color. They skip layout but still repaint the affected area — usually fine for a small element, noticeable on a large one. Animating a big box-shadow is a classic cause of hover jank.

filter is a special case: GPU-accelerated but genuinely costly at scale, especially blur() on large elements.

Diagnosing a janky animation

Open DevTools' Performance panel, record while the animation runs, and look at the flame chart. Purple blocks are layout, green is paint. If you see either repeating every frame during your animation, you've found the cause.

Chrome's Rendering panel is faster for a quick check: enable "Paint flashing" and green highlights show repainted regions. An animation that flashes a large area every frame is doing far too much work.

will-change, and why to be careful

will-change: transform tells the browser to promote an element to its own compositor layer in advance, which can smooth an animation that stutters on its first frame.

It's frequently misused. Each promoted layer consumes GPU memory, and applying will-change broadly — or leaving it on permanently — can degrade performance rather than improve it. Apply it shortly before the animation starts and remove it afterwards, and only when you've measured an actual problem.

Practical rules

Animate transform and opacity by default. Keep interface feedback between 150 and 300ms, and entrances between 300 and 600ms. Limit how many elements animate simultaneously, since even cheap animations add up. Always honour prefers-reduced-motion, which is an accessibility requirement rather than a performance one but belongs in the same habit.

And remember that CSS animations beat JavaScript ones for simple UI motion not just on bundle size, but because the browser can optimise them ahead of time and run them off the main thread — meaning they keep running smoothly even while your JavaScript is busy.

Frequently asked questions

Which CSS properties are cheapest to animate?

transform and opacity. They are handled by the compositor on the GPU, skipping layout and paint entirely, so they stay smooth even when the main thread is busy.

Why is my CSS animation stuttering?

You are most likely animating a layout property such as width, height, top or margin, which forces the browser to recalculate layout every frame. Switch to transform: scale() or translate() instead.

Should I use will-change to speed up animations?

Only when you have measured a real problem. Each promoted layer uses GPU memory, and applying will-change broadly or leaving it on permanently can make performance worse rather than better.