[{"data":1,"prerenderedAt":922},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost":3,"surroundings:\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost":913},{"id":4,"title":5,"body":6,"description":892,"extension":893,"meta":894,"navigation":906,"path":907,"seo":908,"stem":911,"__hash__":912},"content\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002Findex.md","Reducing Style Recalculation Cost",{"type":7,"value":8,"toc":873},"minimark",[9,14,34,44,134,139,179,183,189,195,205,211,215,220,307,311,317,378,385,400,404,407,530,534,537,541,556,674,678,681,685,688,692,718,722,728,740,746,752,756,768,777,786,795,804,813,822,831,835,858,863,866,869],[10,11,13],"h1",{"id":12},"how-to-reduce-style-recalculation-cost","How to Reduce Style Recalculation Cost",[15,16,17,18,23,24,28,29,33],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002F","DOM Size & List Virtualization",", within ",[19,25,27],{"href":26},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". Before the browser can lay out and paint after a change, it must work out which elements have new computed styles: \"Recalculate Style\" in the Performance panel. The cost depends on how many elements are invalidated by the change and how much work it takes to compute each one's style. A class toggled on a small widget might invalidate ten elements. The same class toggled on ",[30,31,32],"code",{},"body"," might invalidate every element on the page.",[15,35,36,37,40,41,43],{},"Style recalculation is often overlooked because it rarely appears in network-focused audits, but on large pages it is a frequent component of slow interactions: a dark mode toggle that takes 150ms, opening a modal that adds ",[30,38,39],{},"modal-open"," to ",[30,42,32],{},", a filter that toggles state classes on a container with 5,000 descendants. Reducing the cost means reducing the number of affected elements per change, and avoiding selector patterns that make invalidation broad.",[15,45,46],{},[47,48,54,55,54,62,54,66,54,69,54,78,54,84,54,91,54,99,54,104,54,111,54,115,54,119,54,123,54,130,54],"svg",{"viewBox":49,"width":50,"role":51,"ariaLabel":52,"style":53},"0 0 760 163","100%","img","Bar chart comparing style recalculation time for the same state change applied at different scopes.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[56,57],"rect",{"className":58,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],"svg-canvas","0","#ffffff",[63,64,65],"title",{},"Recalculate Style for one toggle on a 9,000-element page",[67,68,52],"desc",{},[56,70],{"x":71,"y":71,"width":72,"height":73,"rx":74,"fill":75,"stroke":76,"style":77},"1","758","161","10","none","currentColor","stroke-opacity:0.18",[79,80,65],"text",{"x":81,"y":82,"fill":76,"style":83},"28.0","34.0","font-size:16px;font-weight:700",[79,85,90],{"x":86,"y":87,"fill":76,"style":88,"textAnchor":89},"244.0","70.0","font-size:13px","end","Class on body (descendant selectors)",[56,92],{"x":93,"y":94,"width":95,"height":96,"rx":97,"fill":75,"stroke":76,"style":98},"256.0","56.0","412.0","19","3","fill-opacity:0.7;stroke-opacity:0.35",[79,100,103],{"x":101,"y":87,"fill":76,"style":102},"674.0","font-size:12px;font-weight:600","86ms",[79,105,107,108],{"x":86,"y":106,"fill":76,"style":88,"textAnchor":89},"101.0","Custom property changed on ",[109,110],"root",{},[56,112],{"x":93,"y":113,"width":114,"height":96,"rx":97,"fill":75,"stroke":76,"style":98},"87.0","306.6",[79,116,118],{"x":117,"y":106,"fill":76,"style":102},"568.6","64ms",[79,120,122],{"x":86,"y":121,"fill":76,"style":88,"textAnchor":89},"132.0","Class on the affected component only",[56,124],{"x":93,"y":125,"width":126,"height":96,"rx":97,"fill":127,"stroke":128,"style":129},"118.0","19.2","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[79,131,133],{"x":132,"y":121,"fill":76,"style":102},"281.2","4ms",[135,136,138],"h2",{"id":137},"rapid-diagnosis","Rapid Diagnosis",[140,141,142,150,163,173],"ul",{},[143,144,145,149],"li",{},[146,147,148],"strong",{},"Record the interaction"," and look for long Recalculate Style events; the event details show \"Elements affected\".",[143,151,152,155,156,159,160,162],{},[146,153,154],{},"Check where classes are toggled",": ",[30,157,158],{},"html",", ",[30,161,32],{}," and page wrappers are red flags.",[143,164,165,168,169,172],{},[146,166,167],{},"Look for runtime custom property changes"," on ",[30,170,171],{},":root"," or high ancestors.",[143,174,175,178],{},[146,176,177],{},"Enable selector stats"," (Performance panel setting \"Enable CSS selector stats\") to see which selectors cost most during recalculation.",[135,180,182],{"id":181},"root-cause-analysis","Root Cause Analysis",[15,184,185,188],{},[146,186,187],{},"1. High-level state classes."," Toggling classes near the root invalidates many descendants.",[15,190,191,194],{},[146,192,193],{},"2. Root custom properties changed at runtime."," Every element using inherited properties may need recomputation.",[15,196,197,204],{},[146,198,199,200,203],{},"3. Broad ",[201,202],"has",{},"() and attribute selectors."," Some patterns make the browser check many elements on each change.",[15,206,207,210],{},[146,208,209],{},"4. Large DOMs."," Even well-scoped changes cost more when subtrees are huge.",[135,212,214],{"id":213},"step-by-step-resolution","Step-by-Step Resolution",[216,217,219],"h3",{"id":218},"_1-move-state-classes-to-the-smallest-relevant-container","1. Move state classes to the smallest relevant container",[221,222,227],"pre",{"className":223,"code":224,"language":225,"meta":226,"style":226},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Before: affects every element's style matching.\ndocument.body.classList.toggle('filters-open', open);\n\u002F\u002F After: scoped to the components that actually change.\nfiltersPanel.classList.toggle('is-open', open);\nproductGrid.classList.toggle('is-narrow', open);\n\u002F\u002F trade-off: more specific toggles need more code coordination; a small state\n\u002F\u002F helper that toggles a known list of containers keeps it manageable.\n","javascript","",[30,228,229,238,259,265,280,295,301],{"__ignoreMap":226},[230,231,234],"span",{"class":232,"line":233},"line",1,[230,235,237],{"class":236},"sjfSM","\u002F\u002F Before: affects every element's style matching.\n",[230,239,241,245,249,252,256],{"class":232,"line":240},2,[230,242,244],{"class":243},"saISM","document.body.classList.",[230,246,248],{"class":247},"smZ65","toggle",[230,250,251],{"class":243},"(",[230,253,255],{"class":254},"sZ8jY","'filters-open'",[230,257,258],{"class":243},", open);\n",[230,260,262],{"class":232,"line":261},3,[230,263,264],{"class":236},"\u002F\u002F After: scoped to the components that actually change.\n",[230,266,268,271,273,275,278],{"class":232,"line":267},4,[230,269,270],{"class":243},"filtersPanel.classList.",[230,272,248],{"class":247},[230,274,251],{"class":243},[230,276,277],{"class":254},"'is-open'",[230,279,258],{"class":243},[230,281,283,286,288,290,293],{"class":232,"line":282},5,[230,284,285],{"class":243},"productGrid.classList.",[230,287,248],{"class":247},[230,289,251],{"class":243},[230,291,292],{"class":254},"'is-narrow'",[230,294,258],{"class":243},[230,296,298],{"class":232,"line":297},6,[230,299,300],{"class":236},"\u002F\u002F trade-off: more specific toggles need more code coordination; a small state\n",[230,302,304],{"class":232,"line":303},7,[230,305,306],{"class":236},"\u002F\u002F helper that toggles a known list of containers keeps it manageable.\n",[216,308,310],{"id":309},"_2-scope-custom-properties","2. Scope custom properties",[15,312,313,314,316],{},"Define runtime-changing custom properties on the component that uses them, not on ",[30,315,171],{},". For theme switches, which legitimately affect everything, accept a one-time cost but avoid animating root variables.",[221,318,322],{"className":319,"code":320,"language":321,"meta":226,"style":226},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F* Changing --progress on :root every frame restyles the page; scope it instead. *\u002F\n.uploader { --progress: 0; }\n.uploader .bar { transform: scaleX(var(--progress)); }\n","css",[30,323,324,329,349],{"__ignoreMap":226},[230,325,326],{"class":232,"line":233},[230,327,328],{"class":236},"\u002F* Changing --progress on :root every frame restyles the page; scope it instead. *\u002F\n",[230,330,331,335,338,342,344,346],{"class":232,"line":240},[230,332,334],{"class":333},"sPXB4",".uploader",[230,336,337],{"class":243}," { ",[230,339,341],{"class":340},"sQw3B","--progress",[230,343,155],{"class":243},[230,345,60],{"class":333},[230,347,348],{"class":243},"; }\n",[230,350,351,353,356,358,361,363,366,368,371,373,375],{"class":232,"line":261},[230,352,334],{"class":333},[230,354,355],{"class":333}," .bar",[230,357,337],{"class":243},[230,359,360],{"class":333},"transform",[230,362,155],{"class":243},[230,364,365],{"class":333},"scaleX",[230,367,251],{"class":243},[230,369,370],{"class":333},"var",[230,372,251],{"class":243},[230,374,341],{"class":340},[230,376,377],{"class":243},")); }\n",[216,379,381,382,384],{"id":380},"_3-keep-selectors-anchored-and-specific","3. Keep ",[201,383],{},"() selectors anchored and specific",[15,386,387,388,391,392,395,396,399],{},"Prefer ",[30,389,390],{},".card:has(> .badge)"," over ",[30,393,394],{},"body:has(.modal[open]) *"," style patterns. Anchoring ",[30,397,398],{},":has()"," to a specific subject and using child combinators limits how much the browser must check on DOM changes.",[216,401,403],{"id":402},"_4-reduce-the-elements-affected","4. Reduce the elements affected",[15,405,406],{},"Combine scoping with DOM size reductions (deferring hidden UI, virtualization) so that even necessary global changes affect fewer elements.",[15,408,409],{},[47,410,54,413,54,416,54,419,54,421,54,424,54,426,54,431,54,438,54,442,54,447,54,450,54,454,54,458,54,462,54,465,54,469,54,471,54,474,54,477,54,481,54,483,54,486,54,488,54,491,54,494,54,498,54,500,54,503,54,505,54,511,54,514,54,518,54,522,54,525,54,527,54],{"viewBox":411,"width":50,"role":51,"ariaLabel":412,"style":53},"0 0 760 228","Common style change patterns and how many elements they typically force the browser to restyle.",[56,414],{"className":415,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,417,418],{},"Invalidation patterns and their blast radius",[67,420,412],{},[56,422],{"x":71,"y":71,"width":72,"height":423,"rx":74,"fill":75,"stroke":76,"style":77},"226",[79,425,418],{"x":81,"y":82,"fill":76,"style":83},[56,427],{"x":81,"y":94,"width":428,"height":429,"rx":60,"fill":76,"stroke":76,"style":430},"203.7","30.0","fill-opacity:0.06;stroke-opacity:0.4",[79,432,437],{"x":433,"y":434,"fill":76,"style":435,"textAnchor":436},"38.0","75.5","font-size:12.5px;font-weight:700","start","Pattern",[56,439],{"x":440,"y":94,"width":441,"height":429,"rx":60,"fill":76,"stroke":76,"style":430},"231.7","250.2",[79,443,446],{"x":444,"y":434,"fill":76,"style":435,"textAnchor":445},"356.7","middle","Blast radius",[56,448],{"x":449,"y":94,"width":441,"height":429,"rx":60,"fill":76,"stroke":76,"style":430},"481.8",[79,451,453],{"x":452,"y":434,"fill":76,"style":435,"textAnchor":445},"606.9","Better alternative",[56,455],{"x":81,"y":456,"width":428,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},"86.0","stroke-opacity:0.35",[79,459,461],{"x":433,"y":460,"fill":76,"style":435,"textAnchor":436},"105.5","Class on body for modal-open",[56,463],{"x":440,"y":456,"width":441,"height":429,"rx":60,"fill":127,"stroke":128,"style":464},"fill-opacity:0.24;stroke-opacity:0.9",[79,466,468],{"x":444,"y":460,"fill":76,"style":467,"textAnchor":445},"font-size:12px","whole page",[56,470],{"x":449,"y":456,"width":441,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},[79,472,473],{"x":452,"y":460,"fill":76,"style":467,"textAnchor":445},"Class on modal + scroll lock on html only",[56,475],{"x":81,"y":476,"width":428,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},"116.0",[79,478,480],{"x":433,"y":479,"fill":76,"style":435,"textAnchor":436},"135.5","Root custom property per frame",[56,482],{"x":440,"y":476,"width":441,"height":429,"rx":60,"fill":127,"stroke":128,"style":464},[79,484,485],{"x":444,"y":479,"fill":76,"style":467,"textAnchor":445},"all users of the variable",[56,487],{"x":449,"y":476,"width":441,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},[79,489,490],{"x":452,"y":479,"fill":76,"style":467,"textAnchor":445},"Scope variable to component",[56,492],{"x":81,"y":493,"width":428,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},"146.0",[79,495,497],{"x":433,"y":496,"fill":76,"style":435,"textAnchor":436},"165.5","body:has(.x) descendant rules",[56,499],{"x":440,"y":493,"width":441,"height":429,"rx":60,"fill":127,"stroke":128,"style":464},[79,501,502],{"x":444,"y":496,"fill":76,"style":467,"textAnchor":445},"broad checks",[56,504],{"x":449,"y":493,"width":441,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},[79,506,507,508,510],{"x":452,"y":496,"fill":76,"style":467,"textAnchor":445},"Anchor ",[201,509],{},"() to the component",[56,512],{"x":81,"y":513,"width":428,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},"176.0",[79,515,517],{"x":433,"y":516,"fill":76,"style":435,"textAnchor":436},"195.5","Class on component",[56,519],{"x":440,"y":513,"width":441,"height":429,"rx":60,"fill":520,"stroke":520,"style":521},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[79,523,524],{"x":444,"y":516,"fill":76,"style":467,"textAnchor":445},"component subtree",[56,526],{"x":449,"y":513,"width":441,"height":429,"rx":60,"fill":75,"stroke":76,"style":457},[79,528,529],{"x":452,"y":516,"fill":76,"style":467,"textAnchor":445},"-",[135,531,533],{"id":532},"verification","Verification",[15,535,536],{},"Re-record the interaction: Recalculate Style duration and \"Elements affected\" should drop. Use selector stats to confirm expensive selectors are gone from the top of the list. Check INP for the interaction in the field after release.",[135,538,540],{"id":539},"worked-example-a-banking-web-app","Worked Example: A Banking Web App",[15,542,543,544,547,548,551,552,555],{},"A banking web app toggled a ",[30,545,546],{},"body.theme-dark"," class for dark mode and a ",[30,549,550],{},"body.sidebar-collapsed"," class for its navigation. On account pages with 12,000 elements (long transaction lists), collapsing the sidebar took 220ms, of which 140ms was style recalculation. The team moved the collapse state to the layout grid container, whose descendants mostly did not depend on it, and introduced ",[30,553,554],{},"content-visibility: auto"," for transaction month groups. Style recalculation for the collapse fell to 9ms and INP for the toggle from 260ms to 80ms. The dark mode toggle stayed at the body level — a rare, acceptable one-off cost.",[15,557,558],{},[47,559,54,562,54,565,54,568,54,570,54,587,54,590,54,592,54,597,54,603,54,607,54,610,54,614,54,617,54,620,54,624,54,627,54,631,54,634,54,638,54,641,54,644,54,647,54,651,54,654,54,662,54,666,54,670,54],{"viewBox":560,"width":50,"role":51,"ariaLabel":561,"style":53},"0 0 760 149","Steps for finding and fixing a long Recalculate Style event.",[56,563],{"className":564,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,566,567],{},"Diagnosing an expensive style recalculation",[67,569,561],{},[571,572,573],"defs",{},[574,575,582],"marker",{"id":576,"viewBox":577,"refX":578,"refY":579,"markerWidth":580,"markerHeight":580,"orient":581},"fa9a26afd8","0 0 10 10","9","5","7","auto-start-reverse",[583,584],"path",{"d":585,"fill":76,"style":586},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[56,588],{"x":71,"y":71,"width":72,"height":589,"rx":74,"fill":75,"stroke":76,"style":77},"147",[79,591,567],{"x":81,"y":82,"fill":76,"style":83},[56,593],{"x":81,"y":94,"width":594,"height":595,"rx":596,"fill":520,"stroke":520,"style":521},"108.8","71.0","6",[79,598,602],{"x":599,"y":600,"fill":76,"style":601,"textAnchor":445},"82.4","78.0","font-size:13px;font-weight:700","Record",[79,604,606],{"x":599,"y":605,"fill":76,"style":467,"textAnchor":445},"96.0","interaction trace",[56,608],{"x":609,"y":94,"width":594,"height":595,"rx":596,"fill":520,"stroke":520,"style":521},"176.8",[79,611,613],{"x":612,"y":600,"fill":76,"style":601,"textAnchor":445},"231.2","Inspect",[79,615,616],{"x":612,"y":605,"fill":76,"style":467,"textAnchor":445},"elements affected",[56,618],{"x":619,"y":94,"width":594,"height":595,"rx":596,"fill":520,"stroke":520,"style":521},"325.6",[79,621,623],{"x":622,"y":600,"fill":76,"style":601,"textAnchor":445},"380.0","Find toggle",[79,625,626],{"x":622,"y":605,"fill":76,"style":467,"textAnchor":445},"which class or",[79,628,630],{"x":622,"y":629,"fill":76,"style":467,"textAnchor":445},"112.0","variable",[56,632],{"x":633,"y":94,"width":594,"height":595,"rx":596,"fill":127,"stroke":128,"style":464},"474.4",[79,635,637],{"x":636,"y":600,"fill":76,"style":601,"textAnchor":445},"528.8","Scope",[79,639,640],{"x":636,"y":605,"fill":76,"style":467,"textAnchor":445},"smallest",[79,642,643],{"x":636,"y":629,"fill":76,"style":467,"textAnchor":445},"container",[56,645],{"x":646,"y":94,"width":594,"height":595,"rx":596,"fill":520,"stroke":520,"style":521},"623.2",[79,648,650],{"x":649,"y":600,"fill":76,"style":601,"textAnchor":445},"677.6","Re-record",[79,652,653],{"x":649,"y":605,"fill":76,"style":467,"textAnchor":445},"compare counts",[232,655],{"x1":656,"y1":657,"x2":658,"y2":657,"stroke":76,"strokeWidth":659,"style":660,"markerEnd":661},"136.8","91.5","174.8","1.5","stroke-opacity:0.6","url(#fa9a26afd8)",[232,663],{"x1":664,"y1":657,"x2":665,"y2":657,"stroke":76,"strokeWidth":659,"style":660,"markerEnd":661},"285.6","323.6",[232,667],{"x1":668,"y1":657,"x2":669,"y2":657,"stroke":76,"strokeWidth":659,"style":660,"markerEnd":661},"434.4","472.4",[232,671],{"x1":672,"y1":657,"x2":673,"y2":657,"stroke":76,"strokeWidth":659,"style":660,"markerEnd":661},"583.2","621.2",[135,675,677],{"id":676},"using-css-selector-stats","Using CSS Selector Stats",[15,679,680],{},"Chrome's Performance panel can record per-selector statistics during style recalculation (enable \"CSS selector stats\" in the panel settings before recording). The resulting table shows, for each selector, elapsed time, match attempts and match count. Sort by elapsed time to find selectors that are expensive, then check whether they are needed or can be anchored more tightly. Selectors with many match attempts but few matches are typical candidates: broad descendant or attribute selectors that the browser must test against many elements. Selector stats add overhead to the recording, so use them for diagnosis only.",[135,682,684],{"id":683},"style-cost-during-page-load","Style Cost During Page Load",[15,686,687],{},"On load, style is calculated for every element once, so the cost scales with DOM size and the number of rules that might apply. Two patterns make initial style work larger than necessary: very large stylesheets with many rules that could match (framework CSS with thousands of selectors), and runtime style injection during hydration, which can trigger several full recalculations as new rules arrive. Purging unused CSS and moving to static stylesheets reduce both. On load, aim for one large style calculation rather than several, which you can check by counting Recalculate Style events in the first seconds of a trace.",[135,689,691],{"id":690},"common-mistakes","Common Mistakes",[140,693,694,700,706,712],{},[143,695,696,699],{},[146,697,698],{},"Using body classes for component state."," Global invalidation for local changes.",[143,701,702,705],{},[146,703,704],{},"Animating root custom properties."," Restyles the page every frame.",[143,707,708,711],{},[146,709,710],{},"Ignoring selector stats."," Guessing at expensive selectors instead of measuring.",[143,713,714,717],{},[146,715,716],{},"Assuming selectors don't matter at all."," Most don't, but a few patterns can be costly on large DOMs.",[135,719,721],{"id":720},"edge-cases","Edge Cases",[15,723,724,727],{},[146,725,726],{},"Theme switching."," A full-page restyle is unavoidable when the theme changes; keep it a single toggle, not a transition of root variables.",[15,729,730,733,734,168,737,739],{},[146,731,732],{},"Scroll locking for modals."," Set ",[30,735,736],{},"overflow: hidden",[30,738,158],{}," directly (a single element) instead of a class that cascades to descendants.",[15,741,742,745],{},[146,743,744],{},"Container queries."," Container size changes restyle the container's descendants; keep containers small enough that this stays cheap.",[15,747,748,751],{},[146,749,750],{},"Web components."," Shadow DOM scopes styles, which can limit invalidation to the component's shadow tree.",[135,753,755],{"id":754},"faq","FAQ",[757,758,761,765],"details",{"className":759},[760],"faq-item",[762,763,764],"summary",{},"What is Recalculate Style?",[15,766,767],{},"The browser step that determines computed styles for elements affected by a change. It runs before layout whenever classes, attributes, styles or the DOM change.",[757,769,771,774],{"className":770},[760],[762,772,773],{},"Are complex selectors slow?",[15,775,776],{},"Usually not in isolation. Broad selectors become costly when they must be tested against many elements on each change, particularly on large DOMs.",[757,778,780,783],{"className":779},[760],[762,781,782],{},"Is :has() bad for performance?",[15,784,785],{},"Not inherently. Anchored, specific uses are cheap. Broad patterns on high ancestors can increase invalidation work. Measure with selector stats.",[757,787,789,792],{"className":788},[760],[762,790,791],{},"Do CSS custom properties slow rendering?",[15,793,794],{},"Static ones are fine. Changing a property high in the tree at runtime forces recomputation for elements that inherit it; scope frequently changing variables.",[757,796,798,801],{"className":797},[760],[762,799,800],{},"How do I see how many elements a style change affected?",[15,802,803],{},"Click the Recalculate Style event in a Performance trace; the summary shows the element count.",[757,805,807,810],{"className":806},[760],[762,808,809],{},"Does Shadow DOM reduce style cost?",[15,811,812],{},"It scopes styles to components, which can limit both selector matching and invalidation. It is not a reason to adopt web components on its own, but it helps when you use them.",[757,814,816,819],{"className":815},[760],[762,817,818],{},"Can style recalculation affect LCP?",[15,820,821],{},"The initial style calculation for a large DOM is part of the first rendering work, so very large or complex pages can see a small LCP effect. Interactions are usually affected more.",[757,823,825,828],{"className":824},[760],[762,826,827],{},"Should I avoid :hover styles on large lists?",[15,829,830],{},"No. Hover affects one element at a time and is cheap. The expensive cases are changes that affect many elements at once.",[135,832,834],{"id":833},"related","Related",[140,836,837,844,851],{},[143,838,839,843],{},[19,840,842],{"href":841},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Ffixing-the-excessive-dom-size-warning\u002F","Fixing the excessive DOM size warning"," — fewer elements to restyle.",[143,845,846,850],{},[19,847,849],{"href":848},"\u002Frendering-css-performance\u002Fcritical-css-and-render-blocking-resources\u002Fruntime-css-in-js-performance-cost\u002F","Runtime CSS-in-JS performance cost"," — style injection that triggers recalculation.",[143,852,853,857],{},[19,854,856],{"href":855},"\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002Fisolating-widgets-with-css-contain\u002F","Isolating widgets with CSS contain"," — style containment for widgets.",[859,860,862],"script",{"type":861},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Reduce Style Recalculation Cost\",\n  \"description\": \"What drives style recalculation cost, how to find expensive invalidations, and how to scope state changes so fewer elements need new styles.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Move state classes to the smallest relevant container\",\n      \"text\": \"Move state classes to the smallest relevant container\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Scope custom properties\",\n      \"text\": \"Define runtime-changing custom properties on the component that uses them, not on :root.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Keep :has() selectors anchored and specific\",\n      \"text\": \"Prefer .card:has(> .badge) over body:has(.modal[open]) * style patterns.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Reduce the elements affected\",\n      \"text\": \"Combine scoping with DOM size reductions (deferring hidden UI, virtualization) so that even necessary global changes affect fewer elements.\"\n    }\n  ]\n}\n",[859,864,865],{"type":861},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Reduce Style Recalculation Cost\",\n  \"description\": \"What drives style recalculation cost, how to find expensive invalidations, and how to scope state changes so fewer elements need new styles.\",\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\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002F\"\n  }\n}\n",[859,867,868],{"type":861},"\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\": \"DOM Size & List Virtualization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Reducing Style Recalculation Cost\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002F\"\n    }\n  ]\n}\n",[870,871,872],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":226,"searchDepth":240,"depth":240,"links":874},[875,876,877,884,885,886,887,888,889,890,891],{"id":137,"depth":240,"text":138},{"id":181,"depth":240,"text":182},{"id":213,"depth":240,"text":214,"children":878},[879,880,881,883],{"id":218,"depth":261,"text":219},{"id":309,"depth":261,"text":310},{"id":380,"depth":261,"text":882},"3. Keep () selectors anchored and specific",{"id":402,"depth":261,"text":403},{"id":532,"depth":240,"text":533},{"id":539,"depth":240,"text":540},{"id":676,"depth":240,"text":677},{"id":683,"depth":240,"text":684},{"id":690,"depth":240,"text":691},{"id":720,"depth":240,"text":721},{"id":754,"depth":240,"text":755},{"id":833,"depth":240,"text":834},"What drives style recalculation cost, how to find expensive invalidations, and how to scope state changes so fewer elements need new styles.","md",{"slug":895,"type":896,"breadcrumb":897,"datePublished":905,"dateModified":905},"reducing-style-recalculation-cost","article",[898,901,902,903],{"name":899,"url":900},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":904},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost",{"title":909,"description":910},"Reducing Style Recalculation Cost in the Browser","Long Recalculate Style events slow interactions. Scope class toggles, avoid root-level invalidations, limit costly :has() selectors and custom property churn.","rendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002Findex","JAOFw445dkuEos5K3GRPiigqg0YOFiSwXVIa_8mP4fU",[914,918],{"title":915,"path":916,"stem":917,"children":-1},"Fixing the Excessive DOM Size Warning","\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Ffixing-the-excessive-dom-size-warning","rendering-css-performance\u002Fdom-size-and-list-virtualization\u002Ffixing-the-excessive-dom-size-warning\u002Findex",{"title":919,"path":920,"stem":921,"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",1791308077898]