[{"data":1,"prerenderedAt":1375},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow":3,"surroundings:\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow":1366},{"id":4,"title":5,"body":6,"description":1347,"extension":1348,"meta":1349,"navigation":1359,"path":1360,"seo":1361,"stem":1364,"__hash__":1365},"content\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Findex.md","Layout Thrashing & Forced Reflow",{"type":7,"value":8,"toc":1325},"minimark",[9,14,24,55,58,195,200,219,227,231,244,248,251,255,258,262,265,410,414,421,561,683,687,735,738,741,745,751,767,783,796,810,814,817,869,873,880,884,912,1036,1040,1054,1058,1073,1077,1080,1083,1087,1100,1104,1125,1129,1168,1172,1184,1214,1223,1232,1241,1250,1259,1263,1286,1290,1312,1316,1319,1322],[10,11,13],"h1",{"id":12},"layout-thrashing-forced-reflow-when-javascript-makes-layout-run-again-and-again","Layout Thrashing & Forced Reflow: When JavaScript Makes Layout Run Again and Again",[15,16,17,18,23],"p",{},"This topic is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". Browsers are lazy about layout in a useful way. When JavaScript changes styles or the DOM, the browser marks layout as dirty and waits; it will compute layout once, just before the next frame is painted, no matter how many changes happened in between. This batching is why changing a hundred elements' styles in a loop is normally cheap.",[15,25,26,27,31,32,31,35,31,38,41,42,45,46,50,51,54],{},"The batching breaks the moment JavaScript asks a question that requires up-to-date layout: an element's ",[28,29,30],"code",{},"offsetHeight",", ",[28,33,34],{},"getBoundingClientRect()",[28,36,37],{},"scrollTop",[28,39,40],{},"clientWidth",", or ",[28,43,44],{},"getComputedStyle()"," for a layout-dependent property. If layout is dirty, the browser must compute it immediately, synchronously, before the script can continue. This is a ",[47,48,49],"strong",{},"forced reflow"," (or forced synchronous layout). One forced reflow is often harmless. Many — a write, then a read, then a write, then a read, in a loop — make layout run once per iteration. This is ",[47,52,53],{},"layout thrashing",", and on large pages it can turn a 2ms operation into a 200ms long task.",[15,56,57],{},"Layout thrashing is one of the most common causes of slow interactions. It hides in tooltips and popovers that position themselves, accordions that measure content height, masonry grids, sticky headers that read scroll position, text-fitting utilities, drag-and-drop libraries, and third-party widgets that measure their containers. Because each read looks innocent in isolation, thrashing often goes unnoticed until INP data highlights it.",[15,59,60],{},[61,62,68,69,68,76,68,80,68,83,68,101,68,109,68,115,68,122,68,129,68,132,68,136,68,139,68,143,68,152,68,155,68,158,68,164,68,170,68,174,68,179,68,182,68,186,68,191,68],"svg",{"viewBox":63,"width":64,"role":65,"ariaLabel":66,"style":67},"0 0 760 290","100%","img","Sequence of a script writing a style, then reading geometry, forcing the browser to run layout synchronously.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[70,71],"rect",{"className":72,"x":74,"y":74,"width":64,"height":64,"fill":75},[73],"svg-canvas","0","#ffffff",[77,78,79],"title",{},"How a forced reflow happens",[81,82,66],"desc",{},[84,85,86],"defs",{},[87,88,95],"marker",{"id":89,"viewBox":90,"refX":91,"refY":92,"markerWidth":93,"markerHeight":93,"orient":94},"faf9aac092","0 0 10 10","9","5","7","auto-start-reverse",[96,97],"path",{"d":98,"fill":99,"style":100},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[70,102],{"x":103,"y":103,"width":104,"height":105,"rx":106,"fill":107,"stroke":99,"style":108},"1","758","288","10","none","stroke-opacity:0.18",[110,111,79],"text",{"x":112,"y":113,"fill":99,"style":114},"28.0","34.0","font-size:16px;font-weight:700",[70,116],{"x":117,"y":118,"width":119,"height":113,"rx":120,"fill":99,"stroke":99,"style":121},"70.3","56.0","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[110,123,128],{"x":124,"y":125,"fill":99,"style":126,"textAnchor":127},"145.3","78.0","font-size:13px;font-weight:700","middle","Script",[70,130],{"x":131,"y":118,"width":119,"height":113,"rx":120,"fill":99,"stroke":99,"style":121},"305.0",[110,133,135],{"x":134,"y":125,"fill":99,"style":126,"textAnchor":127},"380.0","Style engine",[70,137],{"x":138,"y":118,"width":119,"height":113,"rx":120,"fill":99,"stroke":99,"style":121},"539.7",[110,140,142],{"x":141,"y":125,"fill":99,"style":126,"textAnchor":127},"614.7","Layout",[144,145],"line",{"x1":124,"y1":146,"x2":124,"y2":147,"stroke":99,"strokeWidth":148,"strokeDashArray":149,"style":151},"90.0","268.0","1.5",[150,150],"4","stroke-opacity:0.35",[144,153],{"x1":134,"y1":146,"x2":134,"y2":147,"stroke":99,"strokeWidth":148,"strokeDashArray":154,"style":151},[150,150],[144,156],{"x1":141,"y1":146,"x2":141,"y2":147,"stroke":99,"strokeWidth":148,"strokeDashArray":157,"style":151},[150,150],[144,159],{"x1":124,"y1":160,"x2":161,"y2":160,"stroke":99,"strokeWidth":148,"style":162,"markerEnd":163},"124.0","378.0","stroke-opacity:0.6","url(#faf9aac092)",[110,165,169],{"x":166,"y":167,"fill":99,"style":168,"textAnchor":127},"262.7","117.0","font-size:12px","el.style.width = '50%'",[144,171],{"x1":134,"y1":172,"x2":173,"y2":172,"stroke":99,"strokeWidth":148,"style":162,"markerEnd":163},"164.0","612.7",[110,175,178],{"x":176,"y":177,"fill":99,"style":168,"textAnchor":127},"497.3","157.0","mark layout dirty",[144,180],{"x1":124,"y1":181,"x2":173,"y2":181,"stroke":99,"strokeWidth":148,"style":162,"markerEnd":163},"204.0",[110,183,185],{"x":134,"y":184,"fill":99,"style":168,"textAnchor":127},"197.0","read el.offsetHeight",[144,187],{"x1":141,"y1":188,"x2":189,"y2":188,"stroke":99,"strokeWidth":148,"strokeDashArray":190,"style":162,"markerEnd":163},"244.0","147.3",[92,150],[110,192,194],{"x":134,"y":193,"fill":99,"style":168,"textAnchor":127},"237.0","synchronous layout result",[196,197,199],"h2",{"id":198},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,201,202,203,206,207,210,211,214,215,218],{},"Forced reflows and thrashing increase the duration of the tasks they occur in. During page load, that adds to ",[47,204,205],{},"Total Blocking Time"," and can delay hydration. During interactions, it increases the processing time of event handlers, which directly increases ",[47,208,209],{},"INP",". Thrashing inside ",[28,212,213],{},"scroll"," or ",[28,216,217],{},"resize"," handlers causes dropped frames and janky scrolling, and thrashing inside animation loops causes stutter.",[15,220,221,222,226],{},"In the Performance panel, forced reflows appear as purple Layout blocks ",[223,224,225],"em",{},"inside"," script execution (nested under a function call), often with a red triangle and the warning \"Forced reflow is a likely performance bottleneck\". Each one carries a stack trace showing which line of code read layout. Thrashing looks like a comb: many thin Layout blocks alternating with script.",[196,228,230],{"id":229},"prerequisites","Prerequisites",[232,233,234,238,241],"ul",{},[235,236,237],"li",{},"A Performance panel recording of the slow interaction or load phase, with 4x CPU throttling.",[235,239,240],{},"Source maps, so stack traces for forced reflows point at your source code.",[235,242,243],{},"Field INP data with attribution (from the web-vitals library or Long Animation Frames), to prioritise which interactions to fix.",[196,245,247],{"id":246},"_1-environment-setup-reproduce-the-slow-interaction","1. Environment Setup: Reproduce the Slow Interaction",[15,249,250],{},"Identify the interactions with the worst INP from field data — for example, opening a filter panel, expanding a product description, or typing in a search box. Reproduce each in the lab with CPU throttling, and record a Performance trace that includes the interaction.",[196,252,254],{"id":253},"_2-capture-a-baseline","2. Capture a Baseline",[15,256,257],{},"For each interaction, record total task duration, time inside Layout events, and number of Layout events. A handler that triggers 50 layouts of 3ms each is the classic thrashing signature.",[196,259,261],{"id":260},"_3-isolate-the-reads-that-force-layout","3. Isolate the Reads That Force Layout",[15,263,264],{},"Click each forced reflow warning to see the stack trace. Common culprits are measurement calls inside loops, utility functions that read and write in one go, and library code. Note which reads happen after writes in the same task.",[266,267,272],"pre",{"className":268,"code":269,"language":270,"meta":271,"style":271},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Typical thrash: each iteration writes then reads.\nfor (const card of cards) {\n  card.style.height = 'auto';                    \u002F\u002F write: layout now dirty\n  const h = card.offsetHeight;                   \u002F\u002F read: forced layout\n  card.style.height = `${Math.ceil(h \u002F 8) * 8}px`; \u002F\u002F write again\n}\n\u002F\u002F trade-off: the code is easy to read but runs layout once per card; on 200 cards\n\u002F\u002F with a complex page this can take hundreds of milliseconds.\n","javascript","",[28,273,274,282,306,325,343,392,398,404],{"__ignoreMap":271},[275,276,278],"span",{"class":144,"line":277},1,[275,279,281],{"class":280},"sjfSM","\u002F\u002F Typical thrash: each iteration writes then reads.\n",[275,283,285,289,293,296,300,303],{"class":144,"line":284},2,[275,286,288],{"class":287},"sPARh","for",[275,290,292],{"class":291},"saISM"," (",[275,294,295],{"class":287},"const",[275,297,299],{"class":298},"sPXB4"," card",[275,301,302],{"class":287}," of",[275,304,305],{"class":291}," cards) {\n",[275,307,309,312,315,319,322],{"class":144,"line":308},3,[275,310,311],{"class":291},"  card.style.height ",[275,313,314],{"class":287},"=",[275,316,318],{"class":317},"sZ8jY"," 'auto'",[275,320,321],{"class":291},";                    ",[275,323,324],{"class":280},"\u002F\u002F write: layout now dirty\n",[275,326,328,331,334,337,340],{"class":144,"line":327},4,[275,329,330],{"class":287},"  const",[275,332,333],{"class":298}," h",[275,335,336],{"class":287}," =",[275,338,339],{"class":291}," card.offsetHeight;                   ",[275,341,342],{"class":280},"\u002F\u002F read: forced layout\n",[275,344,346,348,350,353,356,359,363,366,369,372,375,378,381,383,386,389],{"class":144,"line":345},5,[275,347,311],{"class":291},[275,349,314],{"class":287},[275,351,352],{"class":317}," `${",[275,354,355],{"class":291},"Math",[275,357,358],{"class":317},".",[275,360,362],{"class":361},"smZ65","ceil",[275,364,365],{"class":317},"(",[275,367,368],{"class":291},"h",[275,370,371],{"class":287}," \u002F",[275,373,374],{"class":298}," 8",[275,376,377],{"class":317},") ",[275,379,380],{"class":287},"*",[275,382,374],{"class":298},[275,384,385],{"class":317},"}px`",[275,387,388],{"class":291},"; ",[275,390,391],{"class":280},"\u002F\u002F write again\n",[275,393,395],{"class":144,"line":394},6,[275,396,397],{"class":291},"}\n",[275,399,401],{"class":144,"line":400},7,[275,402,403],{"class":280},"\u002F\u002F trade-off: the code is easy to read but runs layout once per card; on 200 cards\n",[275,405,407],{"class":144,"line":406},8,[275,408,409],{"class":280},"\u002F\u002F with a complex page this can take hundreds of milliseconds.\n",[196,411,413],{"id":412},"_4-apply-separate-reads-from-writes","4. Apply: Separate Reads From Writes",[15,415,416,417,420],{},"Restructure into a read phase and a write phase. Read all geometry first (one layout at most), then apply all writes (layout happens once, at the next frame). Where code is spread across modules, a scheduling helper such as fastdom, or a simple queue run in ",[28,418,419],{},"requestAnimationFrame",", enforces the separation.",[266,422,424],{"className":268,"code":423,"language":270,"meta":271,"style":271},"\u002F\u002F Read phase: one layout.\nfor (const card of cards) card.style.height = 'auto';\nconst heights = cards.map((c) => c.offsetHeight);\n\u002F\u002F Write phase: no reads, so no forced layout.\ncards.forEach((c, i) => { c.style.height = `${Math.ceil(heights[i] \u002F 8) * 8}px`; });\n\u002F\u002F trade-off: the 'auto' reset is itself a write before the reads; grouping all\n\u002F\u002F resets first keeps it to one forced layout instead of one per card.\n",[28,425,426,431,453,483,488,551,556],{"__ignoreMap":271},[275,427,428],{"class":144,"line":277},[275,429,430],{"class":280},"\u002F\u002F Read phase: one layout.\n",[275,432,433,435,437,439,441,443,446,448,450],{"class":144,"line":284},[275,434,288],{"class":287},[275,436,292],{"class":291},[275,438,295],{"class":287},[275,440,299],{"class":298},[275,442,302],{"class":287},[275,444,445],{"class":291}," cards) card.style.height ",[275,447,314],{"class":287},[275,449,318],{"class":317},[275,451,452],{"class":291},";\n",[275,454,455,457,460,462,465,468,471,475,477,480],{"class":144,"line":308},[275,456,295],{"class":287},[275,458,459],{"class":298}," heights",[275,461,336],{"class":287},[275,463,464],{"class":291}," cards.",[275,466,467],{"class":361},"map",[275,469,470],{"class":291},"((",[275,472,474],{"class":473},"sQw3B","c",[275,476,377],{"class":291},[275,478,479],{"class":287},"=>",[275,481,482],{"class":291}," c.offsetHeight);\n",[275,484,485],{"class":144,"line":327},[275,486,487],{"class":280},"\u002F\u002F Write phase: no reads, so no forced layout.\n",[275,489,490,493,496,498,500,502,505,507,509,512,514,516,518,520,522,524,527,530,532,535,538,540,542,544,546,548],{"class":144,"line":345},[275,491,492],{"class":291},"cards.",[275,494,495],{"class":361},"forEach",[275,497,470],{"class":291},[275,499,474],{"class":473},[275,501,31],{"class":291},[275,503,504],{"class":473},"i",[275,506,377],{"class":291},[275,508,479],{"class":287},[275,510,511],{"class":291}," { c.style.height ",[275,513,314],{"class":287},[275,515,352],{"class":317},[275,517,355],{"class":291},[275,519,358],{"class":317},[275,521,362],{"class":361},[275,523,365],{"class":317},[275,525,526],{"class":291},"heights",[275,528,529],{"class":317},"[",[275,531,504],{"class":291},[275,533,534],{"class":317},"] ",[275,536,537],{"class":287},"\u002F",[275,539,374],{"class":298},[275,541,377],{"class":317},[275,543,380],{"class":287},[275,545,374],{"class":298},[275,547,385],{"class":317},[275,549,550],{"class":291},"; });\n",[275,552,553],{"class":144,"line":394},[275,554,555],{"class":280},"\u002F\u002F trade-off: the 'auto' reset is itself a write before the reads; grouping all\n",[275,557,558],{"class":144,"line":400},[275,559,560],{"class":280},"\u002F\u002F resets first keeps it to one forced layout instead of one per card.\n",[15,562,563],{},[61,564,68,567,68,570,68,573,68,575,68,578,68,580,68,588,68,593,68,599,68,605,68,609,68,613,68,617,68,622,68,627,68,632,68,636,68,640,68,643,68,648,68,654,68,658,68,662,68,666,68,669,68,673,68,677,68],{"viewBox":565,"width":64,"role":65,"ariaLabel":566,"style":67},"0 0 760 200","Timeline comparing an interaction handler that forces layout per card with one that batches reads and writes.",[70,568],{"className":569,"x":74,"y":74,"width":64,"height":64,"fill":75},[73],[77,571,572],{},"Interaction task with thrashing vs batched layout (200 cards)",[81,574,566],{},[70,576],{"x":103,"y":103,"width":104,"height":577,"rx":106,"fill":107,"stroke":99,"style":108},"198",[110,579,572],{"x":112,"y":113,"fill":99,"style":114},[144,581],{"x1":582,"y1":583,"x2":582,"y2":584,"stroke":585,"strokeWidth":148,"strokeDashArray":586,"style":587},"517.8","72.0","156.0","#b8860b",[150,150],"stroke-opacity:0.9",[110,589,592],{"x":112,"y":590,"fill":99,"style":591},"96.0","font-size:12.5px;font-weight:700","Thrashing",[70,594],{"x":595,"y":125,"width":596,"height":597,"rx":598,"fill":107,"stroke":99,"style":151},"102.1","539.9","26.0","3",[110,600,604],{"x":601,"y":602,"fill":99,"style":603,"textAnchor":127},"372.0","95.5","font-size:11.5px","script + 200 forced layouts",[70,606],{"x":607,"y":125,"width":608,"height":597,"rx":598,"fill":99,"stroke":99,"style":121},"643.5","61.0",[110,610,612],{"x":611,"y":602,"fill":99,"style":603,"textAnchor":127},"673.9","paint",[110,614,616],{"x":112,"y":615,"fill":99,"style":591},"134.0","Batched",[70,618],{"x":595,"y":619,"width":608,"height":597,"rx":598,"fill":620,"stroke":620,"style":621},"116.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[110,623,626],{"x":624,"y":625,"fill":99,"style":603,"textAnchor":127},"132.6","133.5","script",[70,628],{"x":629,"y":619,"width":608,"height":597,"rx":598,"fill":630,"stroke":585,"style":631},"164.6","#ffc300","fill-opacity:0.24;stroke-opacity:0.9",[110,633,635],{"x":634,"y":625,"fill":99,"style":603,"textAnchor":127},"195.1","1 layout",[70,637],{"x":638,"y":619,"width":639,"height":597,"rx":598,"fill":99,"stroke":99,"style":121},"227.0","50.6",[110,641,612],{"x":642,"y":625,"fill":99,"style":603,"textAnchor":127},"252.3",[144,644],{"x1":645,"y1":584,"x2":646,"y2":584,"stroke":99,"style":647},"101.4","726.0","stroke-opacity:0.4",[110,649,653],{"x":650,"y":651,"fill":652,"style":603,"textAnchor":127},"112.1","173.0","#51617a","0ms",[110,655,657],{"x":656,"y":651,"fill":652,"style":603,"textAnchor":127},"205.5","50ms",[110,659,661],{"x":660,"y":651,"fill":652,"style":603,"textAnchor":127},"309.6","100ms",[110,663,665],{"x":664,"y":651,"fill":652,"style":603,"textAnchor":127},"413.7","150ms",[110,667,668],{"x":582,"y":651,"fill":652,"style":603,"textAnchor":127},"200ms",[110,670,672],{"x":671,"y":651,"fill":652,"style":603,"textAnchor":127},"621.9","250ms",[110,674,676],{"x":675,"y":651,"fill":652,"style":603,"textAnchor":127},"715.3","300ms",[110,678,682],{"x":582,"y":679,"fill":680,"style":681,"textAnchor":127},"68.0","#8a6608","font-size:12px;font-weight:700","INP poor",[196,684,686],{"id":685},"deconstructing-what-forces-layout","Deconstructing What Forces Layout",[15,688,689,690,693,694,697,698,31,701,31,704,707,708,31,710,31,713,715,716,719,720,723,724,727,728,731,732,358],{},"Two kinds of operations interact. ",[47,691,692],{},"Writes"," invalidate layout: changing styles that affect geometry (width, height, padding, font size, display), adding or removing elements, changing text content, toggling classes. ",[47,695,696],{},"Reads"," require valid layout: geometric properties (",[28,699,700],{},"offset*",[28,702,703],{},"client*",[28,705,706],{},"scroll*","), ",[28,709,34],{},[28,711,712],{},"getClientRects()",[28,714,44],{}," for layout-dependent values, ",[28,717,718],{},"innerText"," (which depends on layout), ",[28,721,722],{},"focus()"," (which may scroll), and ",[28,725,726],{},"scrollIntoView()",". Also ",[28,729,730],{},"window.innerHeight"," in some cases, and ",[28,733,734],{},"elementFromPoint()",[15,736,737],{},"A forced reflow happens only when a read follows a write and layout is dirty. Reading many properties in a row after one write forces a single layout; the subsequent reads use the fresh layout. This is why batching works: all reads first, then all writes, then let the browser lay out once at the next frame.",[15,739,740],{},"The cost of each forced layout depends on how much of the page is dirty and how large the DOM is. On a small page, a forced reflow might cost a fraction of a millisecond; on a page with 10,000 elements and no containment, several milliseconds. Containment reduces the scope of each layout, so it makes forced reflows cheaper even when they cannot be avoided entirely.",[196,742,744],{"id":743},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,746,747,750],{},[47,748,749],{},"Third-party code."," Analytics, A\u002FB testing and chat widgets sometimes measure the page repeatedly. The stack trace identifies them; options include configuration, delaying their initialisation, or replacing them.",[15,752,753,756,757,31,760,214,763,766],{},[47,754,755],{},"Framework lifecycle hooks."," Reading layout in ",[28,758,759],{},"useLayoutEffect",[28,761,762],{},"mounted",[28,764,765],{},"ngAfterViewInit"," after other components have written styles in the same commit forces layout; batching measurements in one place helps.",[15,768,769,772,773,776,777,779,780,358],{},[47,770,771],{},"Scroll handlers."," Reading ",[28,774,775],{},"scrollY"," is cheap if layout is clean, but if the handler also writes styles each time, every subsequent scroll event forces layout. Use passive listeners, ",[28,778,419],{}," and ",[28,781,782],{},"IntersectionObserver",[15,784,785,788,789,791,792,795],{},[47,786,787],{},"Resize handlers."," Measuring elements on every ",[28,790,217],{}," event, then writing, thrashes during window resizing; ",[28,793,794],{},"ResizeObserver"," delivers sizes after layout instead.",[15,797,798,801,802,805,806,809],{},[47,799,800],{},"Text measurement."," Fitting text by repeatedly adjusting font size and measuring is a thrash loop; use canvas ",[28,803,804],{},"measureText"," or CSS (",[28,807,808],{},"clamp()",", container query units) instead.",[196,811,813],{"id":812},"validation-and-budgeting","Validation and Budgeting",[15,815,816],{},"After changes, re-record the interaction: the forced reflow warnings should be gone or reduced to one, and Layout time inside script should drop sharply. In the field, track INP for the affected interaction targets. A useful lab budget is \"no forced reflow warnings in key interactions\", enforced by a Lighthouse user-flow script or a Puppeteer trace check.",[818,819,820,833],"table",{},[821,822,823],"thead",{},[824,825,826,830],"tr",{},[827,828,829],"th",{},"Check",[827,831,832],{},"Target",[834,835,836,845,853,861],"tbody",{},[824,837,838,842],{},[839,840,841],"td",{},"Forced reflow warnings in key interactions",[839,843,844],{},"0–1",[824,846,847,850],{},[839,848,849],{},"Layout events per interaction",[839,851,852],{},"1–2",[824,854,855,858],{},[839,856,857],{},"INP for affected targets (p75)",[839,859,860],{},"\u003C 200 ms",[824,862,863,866],{},[839,864,865],{},"Scroll frame drops (lab)",[839,867,868],{},"None visible",[196,870,872],{"id":871},"worked-example-a-product-comparison-table","Worked Example: A Product Comparison Table",[15,874,875,876,879],{},"An electronics retailer's comparison page equalised row heights across columns with a script: for each of 60 specs, it reset heights, measured each of up to 6 product cells, and set the maximum. Opening the comparison or adding a product triggered a 340ms task with 360 forced layouts on a mid-range phone; INP p75 for the \"add to compare\" button was 420ms. Rewriting the logic as a read phase (reset all heights, then measure all cells) and a write phase reduced the task to 45ms with one forced layout. Later, the team replaced the script entirely with CSS grid and ",[28,877,878],{},"subgrid",", which aligns rows natively, removing the measurement altogether. INP p75 dropped to 150ms.",[196,881,883],{"id":882},"replacing-measurement-with-css","Replacing Measurement With CSS",[15,885,886,887,889,890,893,894,897,898,537,901,904,905,908,909,911],{},"The best forced reflow is the one you no longer need. Many measurement scripts exist because CSS once lacked a feature that it now has. Equal-height rows and aligned card content: CSS grid with ",[28,888,878],{},". Animating to ",[28,891,892],{},"height: auto",": ",[28,895,896],{},"interpolate-size: allow-keywords"," or the grid-template-rows ",[28,899,900],{},"0fr",[28,902,903],{},"1fr"," technique. Sticky positioning: ",[28,906,907],{},"position: sticky"," instead of scroll handlers. Responsive components that adapt to their container: container queries instead of measuring width. Text that fits: ",[28,910,808],{}," and container query units. Positioning popovers next to anchors: CSS anchor positioning, where supported. Each removes a class of read-write loops.",[15,913,914],{},[61,915,68,918,68,921,68,924,68,926,68,929,68,931,68,935,68,941,68,945,68,949,68,952,68,956,68,959,68,963,68,965,68,967,68,969,68,972,68,974,68,978,68,980,68,983,68,985,68,988,68,991,68,995,68,997,68,999,68,1001,68,1003,68,1006,68,1010,68,1012,68,1015,68,1017,68,1019,68,1022,68,1026,68,1028,68,1031,68,1033,68],{"viewBox":916,"width":64,"role":65,"ariaLabel":917,"style":67},"0 0 760 258","Common JavaScript measurement patterns that cause forced reflow and the CSS features that replace them.",[70,919],{"className":920,"x":74,"y":74,"width":64,"height":64,"fill":75},[73],[77,922,923],{},"Measurement scripts and their CSS replacements",[81,925,917],{},[70,927],{"x":103,"y":103,"width":104,"height":928,"rx":106,"fill":107,"stroke":99,"style":108},"256",[110,930,923],{"x":112,"y":113,"fill":99,"style":114},[70,932],{"x":112,"y":118,"width":933,"height":934,"rx":74,"fill":99,"stroke":99,"style":121},"208.6","30.0",[110,936,940],{"x":937,"y":938,"fill":99,"style":591,"textAnchor":939},"38.0","75.5","start","Script pattern",[70,942],{"x":943,"y":118,"width":944,"height":934,"rx":74,"fill":99,"stroke":99,"style":121},"236.6","247.7",[110,946,948],{"x":947,"y":938,"fill":99,"style":591,"textAnchor":127},"360.5","CSS replacement",[70,950],{"x":951,"y":118,"width":944,"height":934,"rx":74,"fill":99,"stroke":99,"style":121},"484.3",[110,953,955],{"x":954,"y":938,"fill":99,"style":591,"textAnchor":127},"608.2","Support",[70,957],{"x":112,"y":958,"width":933,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},"86.0",[110,960,962],{"x":937,"y":961,"fill":99,"style":591,"textAnchor":939},"105.5","Equalise row heights",[70,964],{"x":943,"y":958,"width":944,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,966,878],{"x":947,"y":961,"fill":99,"style":168,"textAnchor":127},[70,968],{"x":951,"y":958,"width":944,"height":934,"rx":74,"fill":620,"stroke":620,"style":621},[110,970,971],{"x":954,"y":961,"fill":99,"style":168,"textAnchor":127},"modern browsers",[70,973],{"x":112,"y":619,"width":933,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,975,977],{"x":937,"y":976,"fill":99,"style":591,"textAnchor":939},"135.5","Animate to height auto",[70,979],{"x":943,"y":619,"width":944,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,981,982],{"x":947,"y":976,"fill":99,"style":168,"textAnchor":127},"grid rows 0fr to 1fr",[70,984],{"x":951,"y":619,"width":944,"height":934,"rx":74,"fill":620,"stroke":620,"style":621},[110,986,987],{"x":954,"y":976,"fill":99,"style":168,"textAnchor":127},"broad",[70,989],{"x":112,"y":990,"width":933,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},"146.0",[110,992,994],{"x":937,"y":993,"fill":99,"style":591,"textAnchor":939},"165.5","Sticky header via scroll handler",[70,996],{"x":943,"y":990,"width":944,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,998,907],{"x":947,"y":993,"fill":99,"style":168,"textAnchor":127},[70,1000],{"x":951,"y":990,"width":944,"height":934,"rx":74,"fill":620,"stroke":620,"style":621},[110,1002,987],{"x":954,"y":993,"fill":99,"style":168,"textAnchor":127},[70,1004],{"x":112,"y":1005,"width":933,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},"176.0",[110,1007,1009],{"x":937,"y":1008,"fill":99,"style":591,"textAnchor":939},"195.5","Width-based component variants",[70,1011],{"x":943,"y":1005,"width":944,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,1013,1014],{"x":947,"y":1008,"fill":99,"style":168,"textAnchor":127},"container queries",[70,1016],{"x":951,"y":1005,"width":944,"height":934,"rx":74,"fill":620,"stroke":620,"style":621},[110,1018,971],{"x":954,"y":1008,"fill":99,"style":168,"textAnchor":127},[70,1020],{"x":112,"y":1021,"width":933,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},"206.0",[110,1023,1025],{"x":937,"y":1024,"fill":99,"style":591,"textAnchor":939},"225.5","Position popover at anchor",[70,1027],{"x":943,"y":1021,"width":944,"height":934,"rx":74,"fill":107,"stroke":99,"style":151},[110,1029,1030],{"x":947,"y":1024,"fill":99,"style":168,"textAnchor":127},"anchor positioning",[70,1032],{"x":951,"y":1021,"width":944,"height":934,"rx":74,"fill":99,"stroke":99,"style":121},[110,1034,1035],{"x":954,"y":1024,"fill":99,"style":168,"textAnchor":127},"newer",[196,1037,1039],{"id":1038},"using-observers-instead-of-polling","Using Observers Instead of Polling",[15,1041,1042,1043,1045,1046,1048,1049,779,1051,1053],{},"When measurement is necessary, observers deliver it at the right moment. ",[28,1044,794],{}," reports element sizes after layout has been computed and before paint, so reading the reported sizes never forces a layout. ",[28,1047,782],{}," reports visibility changes asynchronously without reading geometry on the main thread in a loop. Replacing ",[28,1050,217],{},[28,1052,213],{}," event handlers that measure with these observers is often the single change that fixes janky scrolling and resizing.",[196,1055,1057],{"id":1056},"forced-style-recalculation-the-lesser-known-sibling","Forced Style Recalculation: The Lesser-Known Sibling",[15,1059,1060,1061,1064,1065,1068,1069,1072],{},"Not every forced synchronous operation is layout. Calling ",[28,1062,1063],{},"getComputedStyle(el).color"," or reading a CSS custom property after changing classes forces a ",[47,1066,1067],{},"style recalculation"," without a full layout. In the Performance panel these appear as Recalculate Style events nested inside script, again with a warning. They are usually cheaper than forced layouts, but on pages with very large DOMs or broad invalidations — such as toggling a theme class on ",[28,1070,1071],{},"body"," — a single forced style recalculation can take tens of milliseconds. The same discipline applies: read computed values before making changes, or cache them, and avoid reading styles inside loops that also toggle classes.",[196,1074,1076],{"id":1075},"thrashing-in-third-party-and-legacy-code","Thrashing in Third-Party and Legacy Code",[15,1078,1079],{},"Some of the worst thrashing is in code you did not write: jQuery plugins that set heights in loops, carousel libraries that measure every slide on each transition, analytics scripts that check element visibility by reading geometry on scroll, and A\u002FB testing tools that wait for elements by polling their size. You cannot restructure their internals, but you can control when and how often they run. Initialise them after the critical interactions are ready, limit them to the pages that need them, and prefer configuration options that disable automatic measurement. Where a widget is both heavy and thrash-prone, an iframe isolates its layout entirely, because each document lays out independently — at the cost of more memory and less integration.",[15,1081,1082],{},"When evaluating a new library, record a trace of its typical interaction on a throttled device before adopting it. A library that triggers dozens of forced reflows per interaction will drag INP down on every page it is used on, and the problem only grows as pages get bigger.",[196,1084,1086],{"id":1085},"team-practices-that-prevent-thrashing","Team Practices That Prevent Thrashing",[15,1088,1089,1090,31,1092,1095,1096,1099],{},"Thrashing is easiest to prevent at code review time. Agree on a few conventions: geometry reads go through a small set of helpers (or a read\u002Fwrite scheduler), components never measure themselves in a loop over siblings, and new scroll or resize listeners require a note explaining why an observer or CSS cannot do the job. Add an ESLint rule or code search check for ",[28,1091,30],{},[28,1093,1094],{},"getBoundingClientRect"," and similar calls inside loops in files that also set ",[28,1097,1098],{},"style",". These small habits stop most regressions before they ship. They cost almost nothing to follow and save hours of profiling later.",[196,1101,1103],{"id":1102},"a-rollout-plan","A Rollout Plan",[1105,1106,1107,1110,1113,1116,1119,1122],"ol",{},[235,1108,1109],{},"Rank interactions by field INP and identify those with forced reflow warnings in the lab.",[235,1111,1112],{},"For each, find the reading code from the stack trace.",[235,1114,1115],{},"Replace with CSS where a modern feature exists.",[235,1117,1118],{},"Otherwise, separate reads and writes, or move measurements into observers.",[235,1120,1121],{},"Re-record and compare; ship and watch INP for the targets.",[235,1123,1124],{},"Add a lab check that fails if key interactions produce forced reflow warnings.",[196,1126,1128],{"id":1127},"common-pitfalls","Common Pitfalls",[232,1130,1131,1137,1150,1156,1162],{},[235,1132,1133,1136],{},[47,1134,1135],{},"Reading layout in loops after writes."," The textbook thrash.",[235,1138,1139,1142,1143,1146,1147,1149],{},[47,1140,1141],{},"Hiding reads in helpers."," Utilities like ",[28,1144,1145],{},"isVisible(el)"," that call ",[28,1148,34],{}," are easy to call inside write loops.",[235,1151,1152,1155],{},[47,1153,1154],{},"Measuring on scroll and resize events."," Use observers.",[235,1157,1158,1161],{},[47,1159,1160],{},"Assuming frameworks prevent thrashing."," They batch DOM writes, but your effects and third-party libraries can still read after write.",[235,1163,1164,1167],{},[47,1165,1166],{},"Ignoring third-party code."," Widgets can thrash without any of your code involved.",[196,1169,1171],{"id":1170},"faq","FAQ",[1173,1174,1177,1181],"details",{"className":1175},[1176],"faq-item",[1178,1179,1180],"summary",{},"What is layout thrashing?",[15,1182,1183],{},"Repeatedly forcing the browser to recompute layout by alternating style writes and geometry reads in the same task, so layout runs many times instead of once.",[1173,1185,1187,1190],{"className":1186},[1176],[1178,1188,1189],{},"Which properties force layout?",[15,1191,1192,1193,31,1196,31,1199,31,1201,31,1203,1205,1206,1208,1209,779,1211,1213],{},"Geometric reads such as ",[28,1194,1195],{},"offsetWidth",[28,1197,1198],{},"clientHeight",[28,1200,37],{},[28,1202,34],{},[28,1204,44],{}," for layout values, ",[28,1207,718],{},", and methods like ",[28,1210,726],{},[28,1212,722],{}," when layout is dirty.",[1173,1215,1217,1220],{"className":1216},[1176],[1178,1218,1219],{},"Is reading layout always bad?",[15,1221,1222],{},"No. Reading when layout is already clean (for example at the start of a frame, before any writes) is cheap. The problem is reading after writes, repeatedly.",[1173,1224,1226,1229],{"className":1225},[1176],[1178,1227,1228],{},"Does requestAnimationFrame prevent forced reflow?",[15,1230,1231],{},"Not by itself. It schedules work before the next paint; if that work writes then reads, it still forces layout. Use it to group writes after reads have been done.",[1173,1233,1235,1238],{"className":1234},[1176],[1178,1236,1237],{},"How do I find forced reflows?",[15,1239,1240],{},"Record a Performance trace and look for Layout events nested inside script with the \"Forced reflow\" warning; the event's stack trace shows the reading code.",[1173,1242,1244,1247],{"className":1243},[1176],[1178,1245,1246],{},"Do frameworks like React cause layout thrashing?",[15,1248,1249],{},"Framework rendering itself batches DOM writes, but code in layout effects, refs and third-party components can read layout after writes. Profile to find out.",[1173,1251,1253,1256],{"className":1252},[1176],[1178,1254,1255],{},"Can containment reduce the cost of forced reflow?",[15,1257,1258],{},"Yes. If the dirty area is inside a contained subtree, the forced layout is limited to that subtree, which makes each reflow cheaper.",[196,1260,1262],{"id":1261},"guides-in-this-topic","Guides in This Topic",[232,1264,1265,1272,1279],{},[235,1266,1267,1271],{},[19,1268,1270],{"href":1269},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Ffinding-forced-reflows-in-the-performance-panel\u002F","Finding forced reflows in the Performance panel"," — locate the reading code.",[235,1273,1274,1278],{},[19,1275,1277],{"href":1276},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002F","Batching DOM reads and writes"," — restructure code to lay out once.",[235,1280,1281,1285],{},[19,1282,1284],{"href":1283},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fresizeobserver-vs-resize-and-scroll-handlers\u002F","ResizeObserver vs resize and scroll handlers"," — measure without forcing layout.",[196,1287,1289],{"id":1288},"related","Related",[232,1291,1292,1298,1305],{},[235,1293,1294,1297],{},[19,1295,1296],{"href":21},"Rendering & CSS performance"," — the full rendering pipeline.",[235,1299,1300,1304],{},[19,1301,1303],{"href":1302},"\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002F","CSS containment & content-visibility"," — reduce the cost of each layout.",[235,1306,1307,1311],{},[19,1308,1310],{"href":1309},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F","Profiling event handlers for INP"," — the metric thrashing hurts most.",[626,1313,1315],{"type":1314},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Layout Thrashing & Forced Reflow: When JavaScript Makes Layout Run Again and Again\",\n  \"description\": \"Why reading layout after writing styles forces synchronous reflow, how repeated reflows (thrashing) cause long tasks and poor INP, and how to restructure code to avoid them.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Reproduce the Slow Interaction\",\n      \"text\": \"Identify the interactions with the worst INP from field data — for example, opening a filter panel, expanding a product description, or typing in a search box.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline\",\n      \"text\": \"For each interaction, record total task duration, time inside Layout events, and number of Layout events.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate the Reads That Force Layout\",\n      \"text\": \"Click each forced reflow warning to see the stack trace.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply: Separate Reads From Writes\",\n      \"text\": \"Restructure into a read phase and a write phase.\"\n    }\n  ]\n}\n",[626,1317,1318],{"type":1314},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Layout Thrashing & Forced Reflow: When JavaScript Makes Layout Run Again and Again\",\n  \"description\": \"Why reading layout after writing styles forces synchronous reflow, how repeated reflows (thrashing) cause long tasks and poor INP, and how to restructure code to avoid them.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F\"\n  }\n}\n",[626,1320,1321],{"type":1314},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Rendering & CSS Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Layout Thrashing & Forced Reflow\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F\"\n    }\n  ]\n}\n",[1098,1323,1324],{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":271,"searchDepth":284,"depth":284,"links":1326},[1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346],{"id":198,"depth":284,"text":199},{"id":229,"depth":284,"text":230},{"id":246,"depth":284,"text":247},{"id":253,"depth":284,"text":254},{"id":260,"depth":284,"text":261},{"id":412,"depth":284,"text":413},{"id":685,"depth":284,"text":686},{"id":743,"depth":284,"text":744},{"id":812,"depth":284,"text":813},{"id":871,"depth":284,"text":872},{"id":882,"depth":284,"text":883},{"id":1038,"depth":284,"text":1039},{"id":1056,"depth":284,"text":1057},{"id":1075,"depth":284,"text":1076},{"id":1085,"depth":284,"text":1086},{"id":1102,"depth":284,"text":1103},{"id":1127,"depth":284,"text":1128},{"id":1170,"depth":284,"text":1171},{"id":1261,"depth":284,"text":1262},{"id":1288,"depth":284,"text":1289},"Why reading layout after writing styles forces synchronous reflow, how repeated reflows (thrashing) cause long tasks and poor INP, and how to restructure code to avoid them.","md",{"slug":1350,"type":1351,"breadcrumb":1352,"datePublished":1358,"dateModified":1358},"layout-thrashing-and-forced-reflow","topic",[1353,1355,1356],{"name":1354,"url":537},"Home",{"name":22,"url":21},{"name":5,"url":1357},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow",{"title":1362,"description":1363},"Layout Thrashing and Forced Reflow: Find and Fix","Forced synchronous layout happens when JavaScript reads geometry after changing styles. Find forced reflows in DevTools, batch reads and writes, and use observers.","rendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Findex","5kb-gBdHgu6RND_fUJpkjqTLPONYBvinGjOvLfObzVo",[1367,1371],{"title":1368,"path":1369,"stem":1370,"children":-1},"Virtualizing Long Lists in React","\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Fvirtualizing-long-lists-in-react","rendering-css-performance\u002Fdom-size-and-list-virtualization\u002Fvirtualizing-long-lists-in-react\u002Findex",{"title":1372,"path":1373,"stem":1374,"children":-1},"Batching DOM Reads and Writes","\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes","rendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002Findex",1791308069638]