Browser Reflow: What Triggers It and How It Affects Core Web Vitals

Reflow recalculates element positions and blocks the main thread. Learn what triggers it, how to detect it, and how to prevent it.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-10

What is browser reflow?

Reflow is what happens when the browser recalculates the position and size of elements on the page. Every time you change the DOM or modify a CSS property that affects layout, the browser has to figure out where everything goes again. This calculation blocks the main thread. Nothing else happens until it finishes.

A single reflow on a simple page takes microseconds. But trigger hundreds of them during a user interaction and you are looking at hundreds of milliseconds of blocked main thread time. That is enough to fail Interaction to Next Paint.

Last reviewed by Arjen Karel on March 2026

The rendering pipeline

To understand why reflow is expensive, you need to know how the browser renders a page. Every visual change goes through up to five stages:

JavaScript → Style → Layout → Paint → Composite

JavaScript (or CSS) triggers a visual change. The browser recalculates which styles apply. Then it runs layout (reflow) to compute geometry. Then it paints pixels. Finally it composites layers together.

Not every change hits all five stages. That is the key insight. Change width or height and you trigger the full pipeline. Change background-color and you skip layout entirely (paint + composite only). Change transform or opacity and you skip both layout and paint, going straight to composite. The web.dev rendering performance guide covers this pipeline in detail.

Composite-only changes are cheap. Layout changes are not. At 60fps the browser has 16.66ms per frame to do everything. After browser overhead, you get roughly 10ms for your code. Spend that on reflow and you get jank.

What triggers reflow

Two categories of things cause reflow: changes that invalidate the current layout, and JavaScript reads that force the browser to calculate layout immediately.

CSS properties that trigger layout

Changing any of these properties forces the browser to reflow:

  • Dimensions: width, height, padding, border, min-height, max-width
  • Position: top, right, bottom, left, margin
  • Layout mode: display, float, position, flex, grid
  • Text: font-size, font-family, font-weight, line-height, text-align, white-space
  • Content: overflow, word-wrap

Properties like color, background-color, visibility, and box-shadow trigger a repaint but not a reflow. Properties like transform, opacity, and filter trigger neither. These are the properties you want for animations and transitions.

JavaScript properties that force synchronous layout

This is where it gets expensive. Certain JavaScript properties force the browser to calculate layout right now, synchronously, blocking your script. Paul Irish maintains a comprehensive list (5,000+ stars on GitHub). The most common ones:

  • offsetWidth, offsetHeight, offsetTop, offsetLeft
  • clientWidth, clientHeight, clientTop, clientLeft
  • scrollWidth, scrollHeight, scrollTop, scrollLeft
  • getBoundingClientRect()
  • getComputedStyle()
  • innerText (yes, reading innerText forces layout)
  • focus()
  • scrollIntoView()

Reading any of these after changing a layout property forces a synchronous reflow. The browser cannot return the value without calculating layout first. Chrome DevTools flags forced reflows that exceed 30ms as a performance bottleneck.

Layout thrashing: the pattern to avoid

Layout thrashing happens when your code alternates between reading and writing layout properties in a loop. Each read forces a reflow because the previous write invalidated the layout. I see this pattern constantly in carousel scripts, accordion plugins, and analytics code that measures element positions.

// BAD: forces reflow on every iteration
for (const el of elements) {
  el.style.width = box.offsetWidth + 'px'; // read + write = forced reflow
}

Every iteration reads offsetWidth (forcing layout) and then writes style.width (invalidating layout). With 100 elements, that is 100 forced reflows instead of one.

// GOOD: batch the read, then batch the writes
const width = box.offsetWidth; // single read
for (const el of elements) {
  el.style.width = width + 'px'; // writes only, no forced reflows
}

One read, one reflow, done. The web.dev guide on layout thrashing shows this pattern in detail. If you need to read individual element sizes, collect all reads first, then do all writes.

Detecting forced reflow in Chrome DevTools

Open the Performance panel and record a trace. Forced reflows show up as purple "Layout" blocks in the flame chart. If Chrome detects a forced synchronous layout, it adds a red triangle warning. Hover over it and you will see exactly which JavaScript line triggered the reflow.

The Console also logs a warning: "Forced reflow while executing JavaScript took Xms". Anything over 30ms is a problem. I have seen sites where a single scroll event handler triggers 40ms of layout work on every frame.

Lighthouse flags this too. Look for the "Avoid forced reflow" diagnostic in the Performance category.

How reflow affects the Core Web Vitals

Interaction to Next Paint (INP)

Reflow directly impacts INP in two ways. If a forced reflow is already running when the user clicks, the input delay increases because the main thread is blocked. If the click handler itself triggers layout work, the processing time increases. Either way, the presentation delay also grows because the browser must complete layout before it can paint the response.

The INP "good" threshold is 200ms. A single forced reflow of 30ms already consumes 15% of that budget. Layout thrashing in an event handler can easily push INP past 500ms.

Across sites monitored by CoreDash, pages that batch DOM reads and writes show roughly 18% better INP scores compared to pages with layout thrashing patterns.

Largest Contentful Paint (LCP)

During page load, reflow competes for main thread time. Font loading is a common source of this: when a web font arrives and replaces fallback text, the browser reflows every element that uses that font. On a page with a lot of text, that reflow can push LCP 100ms or more later.

Images without explicit width and height attributes cause the same problem. According to the 2025 Web Almanac, 62% of mobile pages still have at least one image without dimensions. When that image loads, the browser reflows the page to accommodate the actual size.

Cumulative Layout Shift (CLS)

Reflow itself does not cause CLS. CLS happens when visible elements move after the user sees them. But reflow from late-loading content (injected ads, unsized images, dynamically inserted elements) is the mechanism behind most layout shifts. Fix the reflow trigger and the layout shift disappears.

CSS transitions that animate layout properties are another source. Transitioning height or margin causes a reflow on every animation frame.

Preventing reflow with modern CSS

Composite-only animations

Animate transform and opacity instead of width, height, top, or left. These run on the GPU compositor thread and skip layout entirely. Want to move an element? Use transform: translateX(). Want to resize it visually? Use transform: scale(). The 2025 Web Almanac found that 40% of mobile pages still use non-composited animations. That is 40% of pages doing unnecessary layout work on every frame.

CSS containment

The contain property tells the browser that an element's internals are independent from the rest of the page. When something changes inside a contained element, the browser only reflows that subtree instead of the whole document.

article {
  contain: content;
}

This is especially useful for pages with a large DOM. The more elements the browser has to check during reflow, the longer it takes. Containment limits the blast radius.

content-visibility

content-visibility: auto tells the browser to skip layout, paint, and style calculations for elements that are offscreen. When Google tested this on a travel blog demo, rendering time dropped from 232ms to 30ms. A 7x improvement.

.section {
  content-visibility: auto;
  contain-intrinsic-size: auto 500px;
}

The contain-intrinsic-size gives the browser a placeholder height so scrollbar calculations remain correct. This property became Baseline Newly Available in September 2025, meaning it works in all major browsers.

Practical checklist

  1. Batch DOM reads before writes. Never alternate between reading layout properties and writing style changes.
  2. Set explicit dimensions on images and embeds. This prevents reflow when the resource loads.
  3. Animate with transform and opacity only. These skip layout and paint entirely.
  4. Use contain: content on independent sections. This limits reflow to the changed subtree.
  5. Add content-visibility: auto to below-the-fold sections. This skips layout for offscreen content.
  6. Prefer flexbox over floats. Flexbox layout is roughly 4x faster than float layout for the same number of elements.
  7. Yield to the main thread between expensive DOM operations to keep the page responsive.
  8. Defer scripts that modify the DOM until after the initial render completes.

Monitor the real impact of these changes with Real User Monitoring. Lab tools like Lighthouse show you the layout cost in isolation, but field data shows whether your users actually experience the improvement.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

CoreDash has MCP built in.

Connect it to Claude or any AI agent. Ask it why your INP spiked last Tuesday.

See how it works
Browser Reflow: What Triggers It and How It Affects Core Web VitalsCore Web Vitals Browser Reflow: What Triggers It and How It Affects Core Web Vitals