Forced reflow
Updated
A forced reflow happens when JavaScript changes the page and then immediately reads a value that depends on the layout, such as offsetWidth, getBoundingClientRect() or scrollTop. The browser cannot answer from the old layout, because the change may have moved things, so it stops the script, recalculates styles and layout on the spot, and only then returns the value. Done once, that costs little. Done in a loop or in a scroll handler, it turns into layout thrashing that blocks the main thread for tens or hundreds of milliseconds. Lighthouse 13 reports it as the insight Forced reflow, and Chrome's console has long shown the related warning "[Violation] Forced reflow while executing JavaScript took … ms".
From PerfDelta's measurements: In the mobile lab test, 24% of 5,642 home pages have a good Total Blocking Time and 54% a poor one. The median is 671 ms, with the middle half between 210 ms and 1505 ms. More in the data section below.
Forced reflow in Lighthouse 13 and PageSpeed Insights
The insight is new in Lighthouse 13 and has no predecessor audit (Lighthouse source code, core/audits/insights/forced-reflow-insight.js, version 13.5, retrieved 18 October 2026). Its description: a forced reflow occurs when JavaScript queries geometric properties after styles have been invalidated by a change to the DOM state. It shows two tables:
- Top function call with the Total reflow time: the script function in which most of the forced layout time was spent.
- Call frames that trigger reflow: the source locations (file, line, column) of the reads that forced the layout, with the time per location. Entries marked "[unattributed]" could not be mapped to a script, often because the code is minified without source maps or comes from an extension.
The insight carries no weight in the performance score. The time it reports is part of the main-thread work that shows up as Total Blocking Time in the lab and, when it happens during clicks, taps or typing, as Interaction to Next Paint in the field. Chrome's documentation sets the target as no forced reflows that take longer than 30 milliseconds (Chrome for Developers, Forced reflow).
What triggers a forced reflow
The browser normally batches work: scripts change the DOM and styles, and style calculation and layout run once before the next frame is painted. A forced reflow breaks that batch. The pattern always has two steps in the same task: a write that invalidates the layout, followed by a read that needs the current layout.
- Writes: changing classes or inline styles, inserting or removing elements, changing text, setting
width,height,toporleft. - Reads:
offsetWidth,offsetHeight,offsetTop,clientWidth,scrollHeight,scrollTop,getBoundingClientRect(),getComputedStyle(),innerText,focus()andscrollIntoView().
Typical sources are equal-height scripts for cards, sticky headers that measure their position on every scroll event, sliders that measure each slide after inserting it, masonry grids, text-truncation scripts, and third-party widgets that resize iframes. Large DOMs make each reflow more expensive, because more elements have to be laid out; the DOM size guide covers that side.
Finding the code in Chrome DevTools
- Open the Performance panel, enable CPU throttling (4x or 6x slowdown) and record a page load or the interaction that feels slow.
- In the Insights sidebar, open Forced reflow. Clicking an entry highlights the matching tasks in the main track.
- In the main track, forced layouts appear as purple Layout events marked with a red triangle. Select one: the summary shows a forced reflow warning and links the function and line that triggered it.
- Load source maps for your bundle so that the call frames point to your own files instead of minified code.
In the field, the Long Animation Frames API reports the same cost per script as forcedStyleAndLayoutDuration, which lets you see whether real users run into it during interactions:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
for (const script of entry.scripts) {
if (script.forcedStyleAndLayoutDuration > 30) {
console.log(script.sourceURL, script.sourceFunctionName, Math.round(script.forcedStyleAndLayoutDuration) + ' ms');
}
}
}
}).observe({ type: 'long-animation-frame', buffered: true });
How to fix forced reflows
1. Read first, then write
The most common cause is a loop that alternates writes and reads. Each iteration invalidates the layout and the next read forces it again:
// Slow: every iteration writes, then reads, so each card forces its own layout
cards.forEach((card) => {
card.style.height = ''; // write: invalidates layout
card.style.height = card.offsetHeight + 'px'; // read forces layout, then write again
});
// Fast: all writes, one read pass, all writes; the browser lays out once
cards.forEach((card) => { card.style.height = ''; });
const max = Math.max(...cards.map((card) => card.offsetHeight));
cards.forEach((card) => { card.style.height = max + 'px'; });
2. Defer measurements to the next frame
If code has to measure after a change, move the read to a point where the browser has done its layout anyway. requestAnimationFrame runs before the next paint; a read inside a nested callback happens after that frame's layout:
element.classList.add('is-open');
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const height = element.getBoundingClientRect().height; // layout already done
updateIndicator(height);
});
});
3. Let observers do the measuring
Instead of measuring on every scroll or resize event, let the browser report changes: ResizeObserver for size changes, IntersectionObserver for visibility and sticky states. Both deliver their results after layout, without forcing it.
const header = document.querySelector('.site-header');
new IntersectionObserver(([entry]) => {
header.classList.toggle('is-stuck', entry.intersectionRatio < 1);
}, { threshold: [1] }).observe(document.querySelector('.header-sentinel'));
4. Replace script layout with CSS
Many scripts that measure exist only because CSS could not do it in the past. Equal-height cards work with Flexbox or Grid, sticky elements with position: sticky, truncated text with line-clamp, aspect ratios with aspect-ratio. Removing the script removes the reflow.
5. Animate transform and opacity
Animating top, left, width or height in JavaScript forces layout on every frame if the code also reads positions. transform and opacity can be animated without layout, and CSS transitions or the Web Animations API keep the work off the main thread where possible.
6. Limit the scope of each layout
contain: layout or contain: content on self-contained components, and content-visibility: auto on long sections below the fold, reduce how much of the page a single reflow has to recalculate.
Third-party scripts and frameworks
When the call frames point to a chat widget, a consent tool or an ad script, the fix is outside your code: update the script, load it after the first interaction, or replace it. The third-party code guide covers the options. In frameworks, forced reflows often come from useLayoutEffect or equivalent hooks that measure after every render; move measurements into observers or run them only when the input actually changed.
Checking the result
Record the same page load or interaction again with the same throttling. The total reflow time in the insight should drop, the marked Layout events should disappear from the affected tasks, and Total Blocking Time should fall in the lab. Whether INP improves for real users shows in the field data after a few weeks. The free PerfDelta website speed test shows TBT and field INP for several pages side by side, so you can see which templates are affected before you open DevTools.
Frequently asked questions
What does "[Violation] Forced reflow while executing JavaScript took 50ms" mean?
Chrome logs this warning when a script forced a layout that took longer than expected. It points to the same problem as the Lighthouse insight; the Performance panel shows which function caused it.
Is a forced reflow always bad?
No. A single measurement after a change costs little on a small page. It becomes a problem when it repeats in loops, scroll handlers or animations, or when the DOM is large, which is why Chrome's documentation targets reflows below 30 milliseconds.
Does the insight affect the Lighthouse score?
Not directly. The time it measures is part of the main-thread work and therefore of Total Blocking Time, which accounts for 30 percent of the score.
What is the difference between reflow and repaint?
Reflow, or layout, calculates the size and position of elements. Repaint draws pixels without changing geometry, for example after a colour change. Reflow is the more expensive step, and only reflow can be forced by reading layout values.
What PerfDelta's measurements show
In the mobile lab test, 24% of 5,642 home pages have a good Total Blocking Time and 54% a poor one. The median is 671 ms, with the middle half between 210 ms and 1505 ms.
PerfDelta does not store forced reflows as a metric of their own; they lengthen main-thread tasks and so show up in the lab as Total Blocking Time and in the field as INP.
By industry, the median ranges from 257 ms in Professional services to 1155 ms in Travel and hospitality.
Data as of October 18, 2026, recalculated daily. Source: PerfDelta's own measurements (Chrome UX Report and Lighthouse); details in the website speed statistics and the methodology.
Thresholds and sources
Sources:
- Chrome for Developers: Forced reflow insight
- Lighthouse: forced-reflow-insight (source code, version 13.5)
- web.dev: Avoid large, complex layouts and layout thrashing
- web.dev: Optimize INP
All sources retrieved on October 18, 2026. Thresholds and documentation change; the linked page is authoritative.
Test your site
Run a free scan to see whether this applies to your pages, on mobile and desktop, with lab and field data side by side.
Run a free testA free account saves your tests and shows how your values change from one test to the next. Daily monitoring with field data and alerts starts with the Solo plan.