Skip to main content

Reflow vs Repaint

Reflow, more commonly called layout, recalculates the size and position of elements. Repaint redraws visual pixels after styles change without necessarily recalculating geometry.

Geometry changes: Style -> Layout/Reflow -> Paint -> Composite
Visual changes: Style -----------------> Paint -> Composite
Layer changes: Style -------------------------> Composite

One-line idea: Reflow answers "where and how large?" while repaint answers "what should it look like?"

Core Concepts​

Where They Fit in Browser Rendering​

After the browser parses HTML and CSS, visual updates can move through several stages:

JavaScript or CSS change
↓
Style calculation
↓
Layout (reflow)
↓
Paint and rasterization
↓
Compositing

Not every update requires every stage. The browser starts from the earliest stage invalidated by the change and performs the later work needed to display the result.

What Is Reflow?​

During layout, the browser calculates the geometry of rendered boxes:

  • width and height
  • position
  • margins and padding
  • text wrapping
  • relationships between parents, children and siblings

Reflow generally means performing some of that layout work again after the page changes. Developer tools and browser documentation may use the terms layout and reflow for the same underlying kind of work.

panel.style.width = "500px";

Changing the panel's width can affect its children, nearby elements and text wrapping. The browser may need to recalculate more than the changed element itself.

Common Reflow Triggers​

Changes that can affect geometry include:

  • adding or removing DOM elements
  • changing width, height, padding, margin or borders
  • changing layout modes such as display, grid or flex properties
  • changing content that affects an element's size
  • loading a font or image without reserved dimensions
  • resizing the viewport or changing device orientation
  • reading geometry while style changes are pending
list.appendChild(newItem);
card.style.padding = "24px";
heading.textContent = "A much longer heading that wraps";

A property does not have one universal cost in every situation. The affected layout area depends on document structure, containment and browser optimization.

What Is Repaint?​

During paint, the browser records and rasterizes visual details such as text, colors, borders, backgrounds and shadows.

A repaint happens when pixels need to be redrawn, even when element geometry stays the same.

button.style.color = "white";
button.style.backgroundColor = "royalblue";

These changes usually affect appearance without changing the button's size or position, so layout may be skipped.

Common Repaint Triggers​

Paint-only changes commonly include:

  • color
  • background-color or background-image
  • border-color
  • box-shadow
  • outline
  • visual content changes that preserve geometry

The browser tries to repaint only invalidated regions rather than the entire page. Large elements, complex effects and frequent updates can still make paint expensive.

Reflow vs Repaint​

AreaReflow or LayoutRepaint
Main questionWhere is the element and how large is it?How should the element's pixels look?
Recalculates geometryYesNo
Typical triggerSize, position or document-flow changeColor, background or shadow change
Later workUsually paint and compositeUsually composite
Possible scopeElement, subtree or wider documentInvalidated visual region
Common riskRepeated or forced layoutLarge or complex paint areas

Reflow is often more expensive because geometry dependencies can spread through the document and the result usually needs painting afterward. This is a useful tendency, not a rule that every repaint is cheap or every layout is slow.

Reflow, Repaint and Compositing​

A Reflow Usually Leads to a Repaint​

When geometry changes, the browser normally needs to redraw the affected content:

Change width
↓
Recalculate layout
↓
Repaint affected pixels
↓
Composite the result

The reverse is not always true. A color change can require repainting without recalculating layout.

Composite-Only Updates​

The browser may paint parts of a page into separate layers and then combine those layers during compositing. Some transform and opacity animations can update layer properties without repeating layout or paint on each frame.

.menu {
transform: translateX(-100%);
transition: transform 200ms ease;
}

.menu.is-open {
transform: translateX(0);
}

This is commonly smoother than animating a geometry property such as left:

/* More likely to require layout and paint on each frame */
.menu {
left: -100%;
transition: left 200ms ease;
}

Compositing is not free. Layers use memory, and browsers decide how to create and manage them. Avoid adding will-change broadly or permanently just to force layer promotion.

Layout Is Not the Same as Layout Shift​

A page can perform layout without visibly moving existing content. A layout shift is an unexpected visible movement measured for visual stability, while layout is the browser's geometry calculation.

Layout work may occur without a visible shift.
A visible shift normally requires updated layout and rendering.

Reserving image dimensions reduces unexpected shifts and can also avoid extra layout when the image arrives:

<img src="profile.jpg" width="320" height="240" alt="User profile" />

Forced Reflow and Layout Thrashing​

Normal Asynchronous Layout​

Browsers normally batch style and layout changes until they need to produce a frame. Several writes can therefore be handled together:

card.classList.add("expanded");
card.setAttribute("aria-expanded", "true");
card.textContent = "Expanded content";

Forced Synchronous Layout​

Some JavaScript properties and methods need current geometry. If styles are pending, reading them can force the browser to run layout immediately.

box.style.width = "400px";       // Write: layout is now invalid
const width = box.offsetWidth; // Read: browser may calculate layout now

Common geometry reads include:

  • offsetWidth and offsetHeight
  • clientWidth and clientHeight
  • scrollWidth and scrollHeight
  • getBoundingClientRect()
  • some computed-style values

These APIs are not bad by themselves. The problem is requesting current geometry repeatedly after invalidating it.

Layout Thrashing​

Alternating a style write and geometry read inside a loop can force repeated layouts:

// Problematic: write, read, write, read...
for (const item of items) {
item.style.width = `${container.offsetWidth / 2}px`;
}

Read first, then perform the writes:

const itemWidth = container.offsetWidth / 2;

for (const item of items) {
item.style.width = `${itemWidth}px`;
}
Thrashing:  Write -> Layout -> Write -> Layout -> Write -> Layout
Batching: Read once -> Write -> Write -> Write -> One later layout

Practical Optimization Patterns​

Batch DOM Reads and Writes​

Collect required measurements before changing styles. Then apply related updates together, often by changing a class:

const currentHeight = panel.getBoundingClientRect().height;

requestAnimationFrame(() => {
panel.classList.add("expanded");
panel.style.setProperty("--previous-height", `${currentHeight}px`);
});

requestAnimationFrame() schedules work near the next frame, but it does not make expensive layout or JavaScript free. The callback still needs to finish quickly.

Prefer Appropriate Animation Properties​

For movement and fading, prefer transform and opacity when they provide the required design:

.toast {
opacity: 0;
transform: translateY(12px);
transition: opacity 160ms, transform 160ms;
}

.toast.is-visible {
opacity: 1;
transform: translateY(0);
}

Do not replace every layout animation blindly. Correct behavior, accessibility and visual quality still matter, and the final result should be measured.

Reduce the Scope of Work​

  • Keep the DOM no larger or deeper than the interface requires.
  • Virtualize long lists when only a small portion is visible.
  • Reserve space for images, ads and asynchronously loaded content.
  • Use CSS containment where a genuine component boundary allows it.
  • Avoid changing high-level containers when only a small child needs updating.
  • Remove paint-heavy effects when profiling shows they are costly.

Measure with Browser Tools​

Use the browser Performance panel to record the exact slow page load, animation or interaction. Inspect:

  • Recalculate Style and Layout events
  • forced-reflow warnings and their JavaScript call sites
  • Paint and raster activity
  • dropped or long frames
  • unexpectedly large paint regions
  • layout-shift events

Chrome's Rendering panel can enable Paint flashing to highlight areas being repainted. Treat this as diagnostic evidence, not a performance score by itself.

Common Mistakes​

MistakeBetter approach
Saying every DOM change causes a full-page reflowExplain that browsers invalidate only the required work when possible
Treating reflow and repaint as independent eventsReflow usually causes later paint, while repaint can occur without reflow
Assuming repaint is always cheapConsider the painted area, visual effect and update frequency
Avoiding all geometry readsAvoid repeated reads after writes; measure when geometry is genuinely needed
Calling any layout calculation a CLS problemSeparate rendering work from unexpected visible layout shifts
Animating top, left, width or height by defaultPrefer compositor-friendly properties when the design allows
Adding will-change to many elementsUse it sparingly and remove it when no longer needed
Assuming requestAnimationFrame() fixes slow codeKeep the scheduled work within the frame budget
Optimizing from a list of CSS properties aloneRecord the real interaction and follow the measured bottleneck

Official References​

Summary​

Reflow recalculates page geometry, while repaint redraws visual pixels; efficient interfaces avoid unnecessary work by batching updates, choosing suitable animation properties and measuring real browser behavior.

Something changes
↓
Did geometry change?
Yes -> Layout/Reflow -> Paint -> Composite
No
↓
Did pixels change?
Yes -> Paint -> Composite
No -> Composite-only update may be possible