[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization":3,"surroundings:\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization":1358},{"id":4,"title":5,"body":6,"description":1338,"extension":1339,"meta":1340,"navigation":1351,"path":1352,"seo":1353,"stem":1356,"__hash__":1357},"content\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Findex.md","DOM Size & List Virtualization",{"type":7,"value":8,"toc":1316},"minimark",[9,14,24,27,43,156,161,176,179,183,196,200,405,412,416,419,423,426,430,437,592,763,767,774,777,781,799,812,822,828,834,838,841,907,911,914,994,998,1009,1013,1025,1029,1035,1039,1060,1063,1066,1070,1077,1081,1102,1106,1138,1142,1154,1163,1172,1184,1193,1202,1211,1220,1229,1238,1247,1251,1274,1278,1301,1306,1309,1312],[10,11,13],"h1",{"id":12},"dom-size-list-virtualization-controlling-the-cost-multiplier","DOM Size & List Virtualization: Controlling the Cost Multiplier",[15,16,17,18,23],"p",{},"This topic is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". Nearly every rendering cost grows with the number of elements on the page. Style recalculation matches rules against elements. Layout computes a box for each one. Paint records them. Accessibility trees mirror them. Memory holds them, along with their event listeners and framework bookkeeping. DOM queries walk them. A page with 15,000 elements does not just take longer to load; it makes every subsequent interaction more expensive, because any change that invalidates style or layout has more work to redo.",[15,25,26],{},"Large DOMs are common on exactly the pages that matter commercially: product listings with filters and hidden variants, admin tables with thousands of rows, feeds with infinite scroll, mega menus with hundreds of links rendered on every page, and server-rendered apps that ship every tab and modal in the initial HTML. Lighthouse flags pages above about 1,400 body elements, warns from roughly 800, and reports maximum depth and the largest child count.",[15,28,29,30,34,35,38,39,42],{},"Controlling DOM size combines three approaches: ",[31,32,33],"strong",{},"trimming"," (simpler markup with fewer wrappers), ",[31,36,37],{},"deferring"," (rendering hidden UI only when it is needed), and ",[31,40,41],{},"virtualizing"," (rendering only the visible part of long lists). This topic covers measuring DOM size, choosing among the approaches, and the style recalculation costs that remain even when the DOM is reasonable.",[15,44,45],{},[46,47,53,54,53,61,53,65,53,68,53,77,53,83,53,90,53,98,53,103,53,107,53,111,53,115,53,119,53,125,53,129,53,133,53,137,53,141,53,145,53,152,53],"svg",{"viewBox":48,"width":49,"role":50,"ariaLabel":51,"style":52},"0 0 760 225","100%","img","Bar chart of element counts by page region on an e-commerce category page.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[55,56],"rect",{"className":57,"x":59,"y":59,"width":49,"height":49,"fill":60},[58],"svg-canvas","0","#ffffff",[62,63,64],"title",{},"Where elements come from on a typical category page",[66,67,51],"desc",{},[55,69],{"x":70,"y":70,"width":71,"height":72,"rx":73,"fill":74,"stroke":75,"style":76},"1","758","223","10","none","currentColor","stroke-opacity:0.18",[78,79,64],"text",{"x":80,"y":81,"fill":75,"style":82},"28.0","34.0","font-size:16px;font-weight:700",[78,84,89],{"x":85,"y":86,"fill":75,"style":87,"textAnchor":88},"201.3","70.0","font-size:13px","end","Product grid (48 cards)",[55,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":74,"stroke":75,"style":97},"213.3","56.0","454.7","19","3","fill-opacity:0.7;stroke-opacity:0.35",[78,99,102],{"x":100,"y":86,"fill":75,"style":101},"674.0","font-size:12px;font-weight:600","4300elements",[78,104,106],{"x":85,"y":105,"fill":75,"style":87,"textAnchor":88},"101.0","Mega menu (always rendered)",[55,108],{"x":92,"y":109,"width":110,"height":95,"rx":96,"fill":74,"stroke":75,"style":97},"87.0","222.1",[78,112,114],{"x":113,"y":105,"fill":75,"style":101},"441.3","2100elements",[78,116,118],{"x":85,"y":117,"fill":75,"style":87,"textAnchor":88},"132.0","Filter sidebar",[55,120],{"x":92,"y":121,"width":122,"height":95,"rx":96,"fill":123,"stroke":123,"style":124},"118.0","126.9","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[78,126,128],{"x":127,"y":117,"fill":75,"style":101},"346.2","1200elements",[78,130,132],{"x":85,"y":131,"fill":75,"style":87,"textAnchor":88},"163.0","Footer",[55,134],{"x":92,"y":135,"width":136,"height":95,"rx":96,"fill":123,"stroke":123,"style":124},"149.0","47.6",[78,138,140],{"x":139,"y":131,"fill":75,"style":101},"266.8","450elements",[78,142,144],{"x":85,"y":143,"fill":75,"style":87,"textAnchor":88},"194.0","Header + hero",[55,146],{"x":92,"y":147,"width":148,"height":95,"rx":96,"fill":149,"stroke":150,"style":151},"180.0","19.0","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[78,153,155],{"x":154,"y":143,"fill":75,"style":101},"238.3","180elements",[157,158,160],"h2",{"id":159},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,162,163,164,167,168,171,172,175],{},"Large DOMs affect all three Core Web Vitals. ",[31,165,166],{},"LCP"," suffers when the initial HTML is large (more bytes, more parsing) and the first layout covers thousands of elements. ",[31,169,170],{},"INP"," suffers most: interactions that change classes or content trigger style and layout over many elements, and frameworks reconcile larger trees. ",[31,173,174],{},"CLS"," can suffer indirectly when late-rendering parts of a heavy page push content around. Memory pressure on low-end phones leads to slower garbage collection pauses and, in extreme cases, tab reloads.",[15,177,178],{},"Field data typically shows a clear relationship between DOM size and INP for the same site: pages with more elements have higher INP at p75. Lab data shows it in Recalculate Style and Layout durations and in the \"Avoid an excessive DOM size\" audit.",[157,180,182],{"id":181},"prerequisites","Prerequisites",[184,185,186,190,193],"ul",{},[187,188,189],"li",{},"DOM size statistics for key templates (element count, depth, maximum children).",[187,191,192],{},"Field INP and LCP by template.",[187,194,195],{},"Knowledge of which UI is rendered but hidden (menus, modals, tabs, off-canvas panels).",[157,197,199],{"id":198},"_1-environment-setup-measure-dom-size","1. Environment Setup: Measure DOM Size",[201,202,207],"pre",{"className":203,"code":204,"language":205,"meta":206,"style":206},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Console: DOM size statistics.\nconst els = document.querySelectorAll('*');\nlet maxDepth = 0, maxChildren = 0;\nfor (const el of els) {\n  let d = 0, p = el; while ((p = p.parentElement)) d++;\n  maxDepth = Math.max(maxDepth, d);\n  maxChildren = Math.max(maxChildren, el.children.length);\n}\nconsole.log({ elements: els.length, maxDepth, maxChildren });\n\u002F\u002F trade-off: this walks the whole DOM and is slow on huge pages; run it as a\n\u002F\u002F one-off diagnostic, not in production code.\n","javascript","",[208,209,210,219,251,276,296,333,350,370,376,393,399],"code",{"__ignoreMap":206},[211,212,215],"span",{"class":213,"line":214},"line",1,[211,216,218],{"class":217},"sjfSM","\u002F\u002F Console: DOM size statistics.\n",[211,220,222,226,230,233,237,241,244,248],{"class":213,"line":221},2,[211,223,225],{"class":224},"sPARh","const",[211,227,229],{"class":228},"sPXB4"," els",[211,231,232],{"class":224}," =",[211,234,236],{"class":235},"saISM"," document.",[211,238,240],{"class":239},"smZ65","querySelectorAll",[211,242,243],{"class":235},"(",[211,245,247],{"class":246},"sZ8jY","'*'",[211,249,250],{"class":235},");\n",[211,252,254,257,260,263,266,269,271,273],{"class":213,"line":253},3,[211,255,256],{"class":224},"let",[211,258,259],{"class":235}," maxDepth ",[211,261,262],{"class":224},"=",[211,264,265],{"class":228}," 0",[211,267,268],{"class":235},", maxChildren ",[211,270,262],{"class":224},[211,272,265],{"class":228},[211,274,275],{"class":235},";\n",[211,277,279,282,285,287,290,293],{"class":213,"line":278},4,[211,280,281],{"class":224},"for",[211,283,284],{"class":235}," (",[211,286,225],{"class":224},[211,288,289],{"class":228}," el",[211,291,292],{"class":224}," of",[211,294,295],{"class":235}," els) {\n",[211,297,299,302,305,307,309,312,314,317,320,323,325,328,331],{"class":213,"line":298},5,[211,300,301],{"class":224},"  let",[211,303,304],{"class":235}," d ",[211,306,262],{"class":224},[211,308,265],{"class":228},[211,310,311],{"class":235},", p ",[211,313,262],{"class":224},[211,315,316],{"class":235}," el; ",[211,318,319],{"class":224},"while",[211,321,322],{"class":235}," ((p ",[211,324,262],{"class":224},[211,326,327],{"class":235}," p.parentElement)) d",[211,329,330],{"class":224},"++",[211,332,275],{"class":235},[211,334,336,339,341,344,347],{"class":213,"line":335},6,[211,337,338],{"class":235},"  maxDepth ",[211,340,262],{"class":224},[211,342,343],{"class":235}," Math.",[211,345,346],{"class":239},"max",[211,348,349],{"class":235},"(maxDepth, d);\n",[211,351,353,356,358,360,362,365,368],{"class":213,"line":352},7,[211,354,355],{"class":235},"  maxChildren ",[211,357,262],{"class":224},[211,359,343],{"class":235},[211,361,346],{"class":239},[211,363,364],{"class":235},"(maxChildren, el.children.",[211,366,367],{"class":228},"length",[211,369,250],{"class":235},[211,371,373],{"class":213,"line":372},8,[211,374,375],{"class":235},"}\n",[211,377,379,382,385,388,390],{"class":213,"line":378},9,[211,380,381],{"class":235},"console.",[211,383,384],{"class":239},"log",[211,386,387],{"class":235},"({ elements: els.",[211,389,367],{"class":228},[211,391,392],{"class":235},", maxDepth, maxChildren });\n",[211,394,396],{"class":213,"line":395},10,[211,397,398],{"class":217},"\u002F\u002F trade-off: this walks the whole DOM and is slow on huge pages; run it as a\n",[211,400,402],{"class":213,"line":401},11,[211,403,404],{"class":217},"\u002F\u002F one-off diagnostic, not in production code.\n",[15,406,407,408,411],{},"Break the count down by region with ",[208,409,410],{},"el.querySelectorAll('*').length"," on major containers to see where elements come from.",[157,413,415],{"id":414},"_2-capture-a-baseline","2. Capture a Baseline",[15,417,418],{},"Record element counts per template, Lighthouse DOM size findings, Recalculate Style and Layout durations on load and on key interactions (with CPU throttling), and field INP per template.",[157,420,422],{"id":421},"_3-isolate-the-biggest-contributors","3. Isolate the Biggest Contributors",[15,424,425],{},"Usually a few regions dominate: listing grids, menus, filters, tables, comments. For each, decide whether elements can be trimmed (simpler markup), deferred (rendered on demand) or virtualized (only visible rows rendered).",[157,427,429],{"id":428},"_4-apply-trim-defer-virtualize","4. Apply: Trim, Defer, Virtualize",[15,431,432,433,436],{},"Trim wrapper elements that exist only for styling (modern grid and flex rarely need them). Render hidden UI such as mega menu panels, modals and inactive tabs only when opened, or use ",[208,434,435],{},"content-visibility"," for content that is rendered but offscreen. Virtualize lists with hundreds or thousands of rows. Paginate or \"load more\" instead of unbounded infinite scroll.",[201,438,442],{"className":439,"code":440,"language":441,"meta":206,"style":206},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003C!-- Before: 6 elements per tag chip -->\n\u003Cdiv class=\"chip-wrap\">\u003Cdiv class=\"chip\">\u003Cspan class=\"chip-inner\">\u003Cspan class=\"icon\">\u003C\u002Fspan>\u003Cspan class=\"label\">Waterproof\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\n\u003C!-- After: 2 elements, same visual via CSS -->\n\u003Cspan class=\"chip\">\u003Ci class=\"icon\" aria-hidden=\"true\">\u003C\u002Fi>Waterproof\u003C\u002Fspan>\n\u003C!-- trade-off: fewer wrappers means CSS must do more (grid, gap, ::before icons);\n     component libraries may need custom variants to avoid their nested markup. -->\n","html",[208,443,444,449,536,541,582,587],{"__ignoreMap":206},[211,445,446],{"class":213,"line":214},[211,447,448],{"class":217},"\u003C!-- Before: 6 elements per tag chip -->\n",[211,450,451,454,458,461,463,466,469,471,473,475,478,480,482,484,486,489,491,493,495,497,500,503,505,507,509,511,513,516,519,521,523,525,527,529,531,533],{"class":213,"line":221},[211,452,453],{"class":235},"\u003C",[211,455,457],{"class":456},"sZBmE","div",[211,459,460],{"class":228}," class",[211,462,262],{"class":235},[211,464,465],{"class":246},"\"chip-wrap\"",[211,467,468],{"class":235},">\u003C",[211,470,457],{"class":456},[211,472,460],{"class":228},[211,474,262],{"class":235},[211,476,477],{"class":246},"\"chip\"",[211,479,468],{"class":235},[211,481,211],{"class":456},[211,483,460],{"class":228},[211,485,262],{"class":235},[211,487,488],{"class":246},"\"chip-inner\"",[211,490,468],{"class":235},[211,492,211],{"class":456},[211,494,460],{"class":228},[211,496,262],{"class":235},[211,498,499],{"class":246},"\"icon\"",[211,501,502],{"class":235},">\u003C\u002F",[211,504,211],{"class":456},[211,506,468],{"class":235},[211,508,211],{"class":456},[211,510,460],{"class":228},[211,512,262],{"class":235},[211,514,515],{"class":246},"\"label\"",[211,517,518],{"class":235},">Waterproof\u003C\u002F",[211,520,211],{"class":456},[211,522,502],{"class":235},[211,524,211],{"class":456},[211,526,502],{"class":235},[211,528,457],{"class":456},[211,530,502],{"class":235},[211,532,457],{"class":456},[211,534,535],{"class":235},">\n",[211,537,538],{"class":213,"line":253},[211,539,540],{"class":217},"\u003C!-- After: 2 elements, same visual via CSS -->\n",[211,542,543,545,547,549,551,553,555,558,560,562,564,567,569,572,574,576,578,580],{"class":213,"line":278},[211,544,453],{"class":235},[211,546,211],{"class":456},[211,548,460],{"class":228},[211,550,262],{"class":235},[211,552,477],{"class":246},[211,554,468],{"class":235},[211,556,557],{"class":456},"i",[211,559,460],{"class":228},[211,561,262],{"class":235},[211,563,499],{"class":246},[211,565,566],{"class":228}," aria-hidden",[211,568,262],{"class":235},[211,570,571],{"class":246},"\"true\"",[211,573,502],{"class":235},[211,575,557],{"class":456},[211,577,518],{"class":235},[211,579,211],{"class":456},[211,581,535],{"class":235},[211,583,584],{"class":213,"line":298},[211,585,586],{"class":217},"\u003C!-- trade-off: fewer wrappers means CSS must do more (grid, gap, ::before icons);\n",[211,588,589],{"class":213,"line":335},[211,590,591],{"class":217},"     component libraries may need custom variants to avoid their nested markup. -->\n",[15,593,594],{},[46,595,53,598,53,601,53,604,53,606,53,623,53,626,53,628,53,634,53,641,53,645,53,649,53,655,53,663,53,669,53,673,53,679,53,683,53,687,53,689,53,692,53,695,53,698,53,702,53,705,53,708,53,712,53,714,53,717,53,720,53,723,53,727,53,730,53,733,53,737,53,739,53,742,53,745,53,748,53,752,53,755,53,759,53],{"viewBox":596,"width":49,"role":50,"ariaLabel":597,"style":52},"0 0 760 413","Decision sequence for choosing how to reduce the elements a region contributes.",[55,599],{"className":600,"x":59,"y":59,"width":49,"height":49,"fill":60},[58],[62,602,603],{},"Trim, defer or virtualize?",[66,605,597],{},[607,608,609],"defs",{},[610,611,618],"marker",{"id":612,"viewBox":613,"refX":614,"refY":615,"markerWidth":616,"markerHeight":616,"orient":617},"fabfb4aaab","0 0 10 10","9","5","7","auto-start-reverse",[619,620],"path",{"d":621,"fill":75,"style":622},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[55,624],{"x":70,"y":70,"width":71,"height":625,"rx":73,"fill":74,"stroke":75,"style":76},"411",[78,627,603],{"x":80,"y":81,"fill":75,"style":82},[55,629],{"x":80,"y":93,"width":630,"height":631,"rx":632,"fill":75,"stroke":75,"style":633},"320.0","52.0","6","fill-opacity:0.06;stroke-opacity:0.4",[78,635,640],{"x":636,"y":637,"fill":75,"style":638,"textAnchor":639},"188.0","78.0","font-size:13px;font-weight:700","middle","Is the region hidden until the user acts (menu,",[78,642,644],{"x":636,"y":643,"fill":75,"style":638,"textAnchor":639},"95.0","modal, tab)?",[55,646],{"x":647,"y":93,"width":630,"height":631,"rx":632,"fill":123,"stroke":123,"style":648},"412.0","fill-opacity:0.14;stroke-opacity:0.9",[78,650,654],{"x":651,"y":652,"fill":75,"style":653,"textAnchor":639},"572.0","86.5","font-size:12.5px","Defer — render on open",[213,656],{"x1":657,"y1":658,"x2":659,"y2":658,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"348.0","82.0","410.0","1.5","stroke-opacity:0.6","url(#fabfb4aaab)",[78,664,668],{"x":665,"y":666,"fill":123,"style":667,"textAnchor":639},"380.0","75.0","font-size:11.5px;font-weight:700","yes",[213,670],{"x1":636,"y1":671,"x2":636,"y2":672,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"108.0","142.0",[78,674,678],{"x":675,"y":676,"fill":677,"style":667},"196.0","130.0","#51617a","no",[55,680],{"x":80,"y":681,"width":630,"height":682,"rx":632,"fill":75,"stroke":75,"style":633},"144.0","35.0",[78,684,686],{"x":636,"y":685,"fill":75,"style":638,"textAnchor":639},"166.0","Is it a list with hundreds or more rows?",[55,688],{"x":647,"y":681,"width":630,"height":682,"rx":632,"fill":123,"stroke":123,"style":648},[78,690,691],{"x":651,"y":685,"fill":75,"style":653,"textAnchor":639},"Virtualize or paginate",[213,693],{"x1":657,"y1":694,"x2":659,"y2":694,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"161.5",[78,696,668],{"x":665,"y":697,"fill":123,"style":667,"textAnchor":639},"154.5",[213,699],{"x1":636,"y1":700,"x2":636,"y2":701,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"179.0","213.0",[78,703,678],{"x":675,"y":704,"fill":677,"style":667},"201.0",[55,706],{"x":80,"y":707,"width":630,"height":682,"rx":632,"fill":75,"stroke":75,"style":633},"215.0",[78,709,711],{"x":636,"y":710,"fill":75,"style":638,"textAnchor":639},"237.0","Is it long, static and offscreen?",[55,713],{"x":647,"y":707,"width":630,"height":682,"rx":632,"fill":123,"stroke":123,"style":648},[78,715,716],{"x":651,"y":710,"fill":75,"style":653,"textAnchor":639},"content-visibility auto",[213,718],{"x1":657,"y1":719,"x2":659,"y2":719,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"232.5",[78,721,668],{"x":665,"y":722,"fill":123,"style":667,"textAnchor":639},"225.5",[213,724],{"x1":636,"y1":725,"x2":636,"y2":726,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"250.0","284.0",[78,728,678],{"x":675,"y":729,"fill":677,"style":667},"272.0",[55,731],{"x":80,"y":732,"width":630,"height":682,"rx":632,"fill":75,"stroke":75,"style":633},"286.0",[78,734,736],{"x":636,"y":735,"fill":75,"style":638,"textAnchor":639},"308.0","Does each item use many wrapper elements?",[55,738],{"x":647,"y":732,"width":630,"height":682,"rx":632,"fill":123,"stroke":123,"style":648},[78,740,741],{"x":651,"y":735,"fill":75,"style":653,"textAnchor":639},"Trim the markup",[213,743],{"x1":657,"y1":744,"x2":659,"y2":744,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"303.5",[78,746,668],{"x":665,"y":747,"fill":123,"style":667,"textAnchor":639},"296.5",[213,749],{"x1":636,"y1":750,"x2":636,"y2":751,"stroke":75,"strokeWidth":660,"style":661,"markerEnd":662},"321.0","355.0",[78,753,678],{"x":675,"y":754,"fill":677,"style":667},"343.0",[55,756],{"x":80,"y":757,"width":630,"height":81,"rx":632,"fill":149,"stroke":150,"style":758},"357.0","fill-opacity:0.24;stroke-opacity:0.9",[78,760,762],{"x":636,"y":761,"fill":75,"style":653,"textAnchor":639},"378.5","Leave it — focus on the largest regions first",[157,764,766],{"id":765},"deconstructing-why-dom-size-hurts-interactions","Deconstructing Why DOM Size Hurts Interactions",[15,768,769,770,773],{},"When a user clicks a filter, a typical sequence follows: the handler updates state, the framework re-renders affected components, the DOM changes, and the browser recalculates style and layout before painting the next frame. Each step scales with size. Framework reconciliation compares more nodes. Style recalculation must determine which elements are affected by changed classes — and changes high in the tree (on ",[208,771,772],{},"body"," or a page wrapper) can invalidate styles for every descendant. Layout must then recompute boxes for everything whose geometry might have changed.",[15,775,776],{},"On a page with 2,000 elements, this might total 30ms on a mid-range phone; on a page with 15,000, it can exceed 200ms for the same interaction. The interaction's own logic did not change; the cost multiplier did. That is why DOM size reductions often improve INP across many interactions at once.",[157,778,780],{"id":779},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,782,783,786,787,790,791,794,795,798],{},[31,784,785],{},"Hidden but rendered."," Elements with ",[208,788,789],{},"visibility: hidden"," or ",[208,792,793],{},"opacity: 0"," still participate in style and layout. ",[208,796,797],{},"display: none"," removes them from layout but they are still in the DOM and in style computation for some selectors. Not rendering them at all is cheapest.",[15,800,801,804,805,790,808,811],{},[31,802,803],{},"Server-rendered tabs and accordions."," SEO is a common reason to render all tab content in HTML. Consider whether the content needs to be indexed; if it does, ",[208,806,807],{},"content-visibility: auto",[208,809,810],{},"hidden=\"until-found\""," keeps it in the DOM while reducing rendering cost.",[15,813,814,817,818,821],{},[31,815,816],{},"Inline SVG icons."," Each inline SVG icon can contribute several elements; hundreds of icons add up. Use ",[208,819,820],{},"\u003Cuse>"," references to a sprite or CSS masks.",[15,823,824,827],{},[31,825,826],{},"Third-party widgets."," Reviews, chat and recommendations widgets can add thousands of elements. Load them on interaction or when scrolled into view.",[15,829,830,833],{},[31,831,832],{},"Very wide DOMs."," Parents with thousands of children (a flat list) make some operations slower; grouping rows helps both virtualization and containment.",[157,835,837],{"id":836},"validation-and-budgeting","Validation and Budgeting",[15,839,840],{},"Budget element counts per template in CI (deterministic and easy to track), for example under 1,500 on content pages and under 3,000 on listing pages, plus a maximum depth of around 32. Verify interaction improvements with traces and field INP by template.",[842,843,844,860],"table",{},[845,846,847],"thead",{},[848,849,850,854,857],"tr",{},[851,852,853],"th",{},"Template",[851,855,856],{},"Element budget",[851,858,859],{},"Max depth",[861,862,863,875,885,896],"tbody",{},[848,864,865,869,872],{},[866,867,868],"td",{},"Article \u002F content page",[866,870,871],{},"1,500",[866,873,874],{},"32",[848,876,877,880,883],{},[866,878,879],{},"Category \u002F listing page",[866,881,882],{},"3,000",[866,884,874],{},[848,886,887,890,893],{},[866,888,889],{},"App dashboard (virtualized)",[866,891,892],{},"3,000 visible",[866,894,895],{},"40",[848,897,898,901,904],{},[866,899,900],{},"Global header + menu",[866,902,903],{},"300 at rest",[866,905,906],{},"20",[157,908,910],{"id":909},"worked-example-a-home-improvement-retailer","Worked Example: A Home Improvement Retailer",[15,912,913],{},"A home improvement retailer's category pages averaged 14,500 elements: a mega menu with 2,100 elements on every page, a product grid with 72 cards at 90 elements each, a filter sidebar with every facet value rendered (1,800 elements), and a 450-element footer. INP p75 was 380ms on mobile. The team rendered mega menu panels on first hover or tap (saving 1,900 elements at rest), cut product card markup from 90 to 38 elements by removing wrappers and merging badges, rendered only the first ten values of each facet with a \"show more\" button, and loaded 24 products initially with \"load more\". Elements at load dropped to 3,100, INP p75 fell to 190ms, and LCP improved by 300ms because the HTML shrank by 40%.",[15,915,916],{},[46,917,53,920,53,923,53,926,53,928,53,931,53,933,53,940,53,945,53,949,53,952,53,956,53,960,53,964,53,968,53,972,53,976,53,980,53,984,53,988,53],{"viewBox":918,"width":49,"role":50,"ariaLabel":919,"style":52},"0 0 760 216","Bar chart of field INP at the 75th percentile on category pages as DOM size reductions were rolled out.",[55,921],{"className":922,"x":59,"y":59,"width":49,"height":49,"fill":60},[58],[62,924,925],{},"INP p75 before and after DOM reductions (category pages)",[66,927,919],{},[55,929],{"x":70,"y":70,"width":71,"height":930,"rx":73,"fill":74,"stroke":75,"style":76},"214",[78,932,925],{"x":80,"y":81,"fill":75,"style":82},[213,934],{"x1":935,"y1":936,"x2":935,"y2":143,"stroke":150,"strokeWidth":660,"strokeDashArray":937,"style":939},"453.2","72.0",[938,938],"4","stroke-opacity:0.9",[78,941,944],{"x":942,"y":943,"fill":75,"style":87,"textAnchor":88},"202.4","92.0","Baseline (14,500 elements)",[55,946],{"x":947,"y":637,"width":948,"height":95,"rx":96,"fill":74,"stroke":75,"style":97},"214.4","453.6",[78,950,951],{"x":100,"y":943,"fill":75,"style":101},"380ms",[78,953,955],{"x":942,"y":954,"fill":75,"style":87,"textAnchor":88},"123.0","Mega menu on demand",[55,957],{"x":947,"y":958,"width":959,"height":95,"rx":96,"fill":149,"stroke":150,"style":151},"109.0","358.1",[78,961,963],{"x":962,"y":954,"fill":75,"style":101},"578.5","300ms",[78,965,967],{"x":942,"y":966,"fill":75,"style":87,"textAnchor":88},"154.0","+ lighter cards",[55,969],{"x":947,"y":970,"width":971,"height":95,"rx":96,"fill":149,"stroke":150,"style":151},"140.0","286.5",[78,973,975],{"x":974,"y":966,"fill":75,"style":101},"506.9","240ms",[78,977,979],{"x":942,"y":978,"fill":75,"style":87,"textAnchor":88},"185.0","+ facets and load more (3,100)",[55,981],{"x":947,"y":982,"width":983,"height":95,"rx":96,"fill":149,"stroke":150,"style":151},"171.0","226.8",[78,985,987],{"x":986,"y":978,"fill":75,"style":101},"447.2","190ms",[78,989,993],{"x":935,"y":990,"fill":991,"style":992,"textAnchor":639},"68.0","#8a6608","font-size:12px;font-weight:700","good",[157,995,997],{"id":996},"deferring-hidden-ui-without-hurting-usability","Deferring Hidden UI Without Hurting Usability",[15,999,1000,1001,1004,1005,1008],{},"Rendering menus and modals on demand risks a delay when users open them. Mitigate it by preparing on intent: start rendering a mega menu panel on ",[208,1002,1003],{},"pointerenter"," of its trigger (before the click), or render all panels during idle time after load with ",[208,1006,1007],{},"requestIdleCallback",". Keep the first-open render fast by keeping panel markup simple. For keyboard and screen reader users, make sure triggers have correct ARIA attributes and that focus moves into newly rendered content.",[157,1010,1012],{"id":1011},"style-recalculation-beyond-element-count","Style Recalculation Beyond Element Count",[15,1014,1015,1016,790,1018,1020,1021,1024],{},"Even with a reasonable DOM, style recalculation can be expensive if changes invalidate many elements. Toggling a class on ",[208,1017,441],{},[208,1019,772],{}," (for theming, modal-open states, or scroll-locking) invalidates styles across the whole document. Complex ",[208,1022,1023],{},":has()"," selectors can make invalidation broader. CSS custom properties defined on the root and changed at runtime affect every element that uses them. The guide on reducing style recalculation cost covers scoping these changes.",[157,1026,1028],{"id":1027},"virtualization-trade-offs","Virtualization Trade-offs",[15,1030,1031,1032,1034],{},"Virtualization renders only rows near the viewport and recycles them as the user scrolls. It caps DOM size for arbitrarily long lists, but it has costs: browser find-in-page cannot find rows that are not rendered, screen readers see only rendered rows unless ARIA attributes describe the full set, scroll restoration and anchor links need custom handling, and variable-height rows require measurement. For lists under a few hundred items, ",[208,1033,435],{}," or pagination is often simpler. For thousands of rows, virtualization is usually necessary.",[157,1036,1038],{"id":1037},"framework-patterns-that-inflate-the-dom","Framework Patterns That Inflate the DOM",[15,1040,1041,1042,1045,1046,1045,1049,1052,1053,1055,1056,1059],{},"Component frameworks make it easy to grow the DOM without noticing. Every layer of abstraction tends to add a wrapper element: a ",[208,1043,1044],{},"Card"," that wraps a ",[208,1047,1048],{},"Box",[208,1050,1051],{},"Stack",", each rendering a ",[208,1054,457],{},". Repeated across 50 cards, three unnecessary wrappers add 150 elements; across a design system used everywhere, they add thousands. Use fragments where a component does not need its own box, prefer components that accept an ",[208,1057,1058],{},"as"," prop or render their children directly, and check the rendered DOM of your most repeated components periodically.",[15,1061,1062],{},"Conditional rendering is the other lever. Frameworks make it trivial to render a modal or menu always and toggle its visibility with a prop — and equally trivial to not render it until it is open. Prefer the latter for heavy, rarely used UI. For UI that must open instantly, pre-render on intent (hover, focus or idle time) rather than on page load.",[15,1064,1065],{},"Server-rendered apps add one more source: hydration requires the server and client to produce the same markup, so pages often render hidden variants (mobile and desktop navigation both rendered, one hidden by CSS). Rendering one responsive navigation, or deciding the variant on the server from client hints, can remove hundreds of elements.",[157,1067,1069],{"id":1068},"monitoring-dom-size-over-time","Monitoring DOM Size Over Time",[15,1071,1072,1073,1076],{},"DOM size creeps up release by release. Track it the same way as bundle size: a CI job loads each key template in a headless browser, counts elements, and compares against a budget, failing or warning when a template exceeds it. Store the numbers per build to see trends. In the field, a lightweight RUM snippet can report ",[208,1074,1075],{},"document.getElementsByTagName('*').length"," once after load, sampled, alongside INP. Pairing the two lets you show the relationship between DOM size and responsiveness on your own site, which makes the case for markup budgets far more persuasive than general guidance.",[157,1078,1080],{"id":1079},"a-rollout-plan","A Rollout Plan",[1082,1083,1084,1087,1090,1093,1096,1099],"ol",{},[187,1085,1086],{},"Measure element counts per template and per region.",[187,1088,1089],{},"Defer the largest hidden regions (menus, modals, inactive tabs).",[187,1091,1092],{},"Trim markup in the most repeated components (cards, rows, chips).",[187,1094,1095],{},"Virtualize or paginate the longest lists.",[187,1097,1098],{},"Add element-count budgets to CI.",[187,1100,1101],{},"Track INP by template to confirm improvements.",[157,1103,1105],{"id":1104},"common-pitfalls","Common Pitfalls",[184,1107,1108,1114,1120,1126,1132],{},[187,1109,1110,1113],{},[31,1111,1112],{},"Rendering every menu, modal and tab up front."," Thousands of hidden elements on every page.",[187,1115,1116,1119],{},[31,1117,1118],{},"Deep wrapper nesting from component libraries."," Each layer multiplies across repeated components.",[187,1121,1122,1125],{},[31,1123,1124],{},"Unbounded infinite scroll."," DOM grows for as long as the user scrolls.",[187,1127,1128,1131],{},[31,1129,1130],{},"Virtualizing short lists."," Complexity without benefit; use simpler techniques.",[187,1133,1134,1137],{},[31,1135,1136],{},"Inline SVG icons repeated hundreds of times."," Use sprites.",[157,1139,1141],{"id":1140},"faq","FAQ",[1143,1144,1147,1151],"details",{"className":1145},[1146],"faq-item",[1148,1149,1150],"summary",{},"What is an excessive DOM size?",[15,1152,1153],{},"Lighthouse flags pages with more than about 1,400 body elements and warns above roughly 800. It also reports DOM depth over 32 and parents with more than 60 children.",[1143,1155,1157,1160],{"className":1156},[1146],[1148,1158,1159],{},"Does DOM size affect LCP?",[15,1161,1162],{},"Yes, through larger HTML and longer initial style and layout. The effect on INP is usually larger, because every interaction pays the cost.",[1143,1164,1166,1169],{"className":1165},[1146],[1148,1167,1168],{},"Is display: none enough to reduce DOM cost?",[15,1170,1171],{},"It removes elements from layout and paint, but they remain in the DOM, consume memory and are considered in some style work. Not rendering them is cheaper.",[1143,1173,1175,1178],{"className":1174},[1146],[1148,1176,1177],{},"Should I virtualize every list?",[15,1179,1180,1181,1183],{},"No. Virtualize lists with hundreds or thousands of items. Shorter lists are better served by pagination, ",[208,1182,435],{}," or simply lean markup.",[1143,1185,1187,1190],{"className":1186},[1146],[1148,1188,1189],{},"Does virtualization hurt SEO?",[15,1191,1192],{},"Content not rendered in the HTML is not visible to crawlers that do not scroll. For indexable lists, server-render the first page and provide paginated URLs.",[1143,1194,1196,1199],{"className":1195},[1146],[1148,1197,1198],{},"How do I reduce DOM size in React?",[15,1200,1201],{},"Avoid rendering hidden UI, flatten wrapper components (fragments help), virtualize long lists, and avoid component libraries that wrap each element in several divs.",[1143,1203,1205,1208],{"className":1204},[1146],[1148,1206,1207],{},"Does DOM depth matter as much as count?",[15,1209,1210],{},"Count matters more for overall cost. Depth affects some operations, like style inheritance and layout of nested flex or grid containers, and very deep trees often indicate unnecessary wrappers.",[1143,1212,1214,1217],{"className":1213},[1146],[1148,1215,1216],{},"Do comments and text nodes count towards DOM size?",[15,1218,1219],{},"Lighthouse counts elements only. Text nodes and comments add to HTML size and memory but not to the reported element count; whitespace-only text nodes are cheap.",[1143,1221,1223,1226],{"className":1222},[1146],[1148,1224,1225],{},"Is a large DOM a problem if the page is static?",[15,1227,1228],{},"Less so than on interactive pages, because the costs repeat with each interaction. A static article with 3,000 elements may perform fine, but the same count on a page with filters and live updates usually hurts INP.",[1143,1230,1232,1235],{"className":1231},[1146],[1148,1233,1234],{},"How does DOM size affect memory on mobile?",[15,1236,1237],{},"Each element carries style, layout and accessibility data, plus framework bookkeeping and event listeners. On low-memory phones, pages with tens of thousands of elements trigger more garbage collection and can be discarded when the tab is backgrounded.",[1143,1239,1241,1244],{"className":1240},[1146],[1148,1242,1243],{},"Where should I start?",[15,1245,1246],{},"With the largest region on your highest-traffic template. One deferred mega menu or one lighter product card often removes more elements than every other change combined.",[157,1248,1250],{"id":1249},"guides-in-this-topic","Guides in This Topic",[184,1252,1253,1260,1267],{},[187,1254,1255,1259],{},[19,1256,1258],{"href":1257},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Fvirtualizing-long-lists-in-react\u002F","Virtualizing long lists in React"," — render only visible rows.",[187,1261,1262,1266],{},[19,1263,1265],{"href":1264},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Ffixing-the-excessive-dom-size-warning\u002F","Fixing the excessive DOM size warning"," — find and remove the elements Lighthouse counts.",[187,1268,1269,1273],{},[19,1270,1272],{"href":1271},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Freducing-style-recalculation-cost\u002F","Reducing style recalculation cost"," — scope style invalidations.",[157,1275,1277],{"id":1276},"related","Related",[184,1279,1280,1287,1294],{},[187,1281,1282,1286],{},[19,1283,1285],{"href":1284},"\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002F","CSS containment & content-visibility"," — skip rendering without removing elements.",[187,1288,1289,1293],{},[19,1290,1292],{"href":1291},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-on-infinite-scroll-and-load-more\u002F","Fixing CLS on infinite scroll and load more"," — the visual-stability side of long lists.",[187,1295,1296,1300],{},[19,1297,1299],{"href":1298},"\u002Fframework-performance\u002Freact-rendering-performance\u002F","React performance"," — rendering costs in React apps.",[1302,1303,1305],"script",{"type":1304},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"DOM Size & List Virtualization: Controlling the Cost Multiplier\",\n  \"description\": \"Why DOM size multiplies every rendering cost, how to measure it, and the techniques — trimming, deferring and virtualizing — that keep it under control.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Measure DOM Size\",\n      \"text\": \"Break the count down by region with el.querySelectorAll('*').length on major containers to see where elements come from.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline\",\n      \"text\": \"Record element counts per template, Lighthouse DOM size findings, Recalculate Style and Layout durations on load and on key interactions (with CPU throttling), and field INP per template.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate the Biggest Contributors\",\n      \"text\": \"Usually a few regions dominate: listing grids, menus, filters, tables, comments.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply: Trim, Defer, Virtualize\",\n      \"text\": \"Trim wrapper elements that exist only for styling (modern grid and flex rarely need them).\"\n    }\n  ]\n}\n",[1302,1307,1308],{"type":1304},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"DOM Size & List Virtualization: Controlling the Cost Multiplier\",\n  \"description\": \"Why DOM size multiplies every rendering cost, how to measure it, and the techniques — trimming, deferring and virtualizing — that keep it under control.\",\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\u002F\"\n  }\n}\n",[1302,1310,1311],{"type":1304},"\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}\n",[1313,1314,1315],"style",{},"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":206,"searchDepth":221,"depth":221,"links":1317},[1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337],{"id":159,"depth":221,"text":160},{"id":181,"depth":221,"text":182},{"id":198,"depth":221,"text":199},{"id":414,"depth":221,"text":415},{"id":421,"depth":221,"text":422},{"id":428,"depth":221,"text":429},{"id":765,"depth":221,"text":766},{"id":779,"depth":221,"text":780},{"id":836,"depth":221,"text":837},{"id":909,"depth":221,"text":910},{"id":996,"depth":221,"text":997},{"id":1011,"depth":221,"text":1012},{"id":1027,"depth":221,"text":1028},{"id":1037,"depth":221,"text":1038},{"id":1068,"depth":221,"text":1069},{"id":1079,"depth":221,"text":1080},{"id":1104,"depth":221,"text":1105},{"id":1140,"depth":221,"text":1141},{"id":1249,"depth":221,"text":1250},{"id":1276,"depth":221,"text":1277},"Why DOM size multiplies every rendering cost, how to measure it, and the techniques — trimming, deferring and virtualizing — that keep it under control.","md",{"slug":1341,"type":1342,"breadcrumb":1343,"datePublished":1350,"dateModified":1350},"dom-size-and-list-virtualization","topic",[1344,1347,1348],{"name":1345,"url":1346},"Home","\u002F",{"name":22,"url":21},{"name":5,"url":1349},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization",{"title":1354,"description":1355},"DOM Size and List Virtualization for Faster Pages","Large DOMs slow style, layout, memory and INP. Measure element counts, trim wrapper markup, defer hidden UI and virtualize long lists without breaking accessibility.","rendering-css-performance\u002Fdom-size-and-list-virtualization\u002Findex","5VdJ5q8b4iig6JjD4OmCHim5sQnCqFW3-9EEU2lc2WM",[1359,1363],{"title":1360,"path":1361,"stem":1362,"children":-1},"Using content-visibility: auto on Long Pages","\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002Fusing-content-visibility-auto-on-long-pages","rendering-css-performance\u002Fcss-containment-and-content-visibility\u002Fusing-content-visibility-auto-on-long-pages\u002Findex",{"title":1364,"path":1365,"stem":1366,"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",1791308073247]