CSSRepaints & Reflows

Repaints & Reflows

Not all CSS changes cost the same amount of browser work. Some changes force the browser to recompute the size and position of elements on the page (a reflow, also called layout); others only require redrawing pixels without touching layout at all (a repaint). Understanding this distinction is one of the highest-leverage things you can learn for CSS animation and interaction performance.
Reflow (layout)
A reflow recalculates the geometry — size, position, and how much space it takes from its siblings — of an element, and often its ancestors and siblings too, since changing one box's dimensions can shift everything around it. It's triggered by changes like adjusting width, height, top/left, margin, or by adding/removing elements from the DOM. Reflow is the most expensive step in the rendering pipeline, because its cost can cascade across a large part of the page.
Repaint
A repaint redraws the pixels of an element in its existing geometry, without recalculating layout at all. It's triggered by changes like background-color, visibility, or box-shadow — the element's size and position don't change, so the browser can skip the layout step entirely and go straight to redrawing.
Which properties trigger what

This maps to the well-known "CSS triggers" concept — a reference classification of which rendering stages a given property change forces the browser to run:

Category

Triggers

Example properties

Layout + paint + composite

The most expensive tier — full reflow, then repaint, then recomposite

width, height, top, left, margin, padding, font-size, adding/removing DOM nodes

Paint + composite only

Skips layout — geometry is unchanged, only pixels are redrawn

background-color, color, box-shadow, border-color, visibility

Composite only

The cheapest tier — handled by the GPU compositor with no layout or paint step at all

transform, opacity

Reference: csstriggers.com
The community-maintained "CSS Triggers" reference documented exactly this breakdown, per property, per browser engine — worth searching for if you want to check a specific property you haven't memorized the tier for.
Why transform/opacity are dramatically cheaper to animate

This is one of the most cited performance principles in CSS animation work, and it follows directly from the table above. Compare two ways of moving an element across the screen:

CSS
/* Expensive: animating 'left' triggers a reflow on every frame */
.slide-in-slow {
  position: absolute;
  left: -300px;
  transition: left 0.3s ease-out;
}
.slide-in-slow.active {
  left: 0;
}

/* Cheap: animating 'transform' skips layout entirely — composite-only */
.slide-in-fast {
  transform: translateX(-300px);
  transition: transform 0.3s ease-out;
}
.slide-in-fast.active {
  transform: translateX(0);
}
Both produce the same visual motion, but .slide-in-slow forces the browser to recompute layout for that element (and potentially its neighbors) on every single animation frame, while .slide-in-fast can run the entire animation on the GPU compositor thread without ever touching layout or paint — which is why it stays smooth even on a busy main thread, and why it's the standard recommendation for any animated movement, fading, or scaling.
  • Prefer transform: translate() over animating top/left/margin.

  • Prefer transform: scale() over animating width/height.

  • Prefer opacity over visibility when the element still needs to occupy space, or combine both when it doesn't.

Layout thrashing: the JS-triggered version of this problem
Reflows get especially expensive when JavaScript reads a layout property (like offsetHeight) immediately after writing one, inside a loop — each read forces the browser to flush any pending layout work synchronously. If you're debugging jank during scroll or resize handlers, check for this pattern alongside checking which CSS properties are being changed.