Every HTML element on a page becomes a node in the DOM, and every node has a cost. The browser has to compute its style, place it in the layout, paint it and keep it in memory. While there are a few hundred nodes nobody notices; when there are tens of thousands, as happens with long lists, large tables, huge menus or page builders that nest containers inside containers, every operation gets slower.
Where a large DOM costs you
The cost is not concentrated in a single moment:
- loading: more HTML to download and parse, more nodes to build;
- style calculation: on every change the browser must check which CSS rules apply to which elements, and with complex selectors the work grows with the number of nodes;
- layout: changing the size of one element can force the position of many others to be recalculated;
- interactions: a click that adds a class to
bodycan trigger a style recalculation for the whole page, and that time ends up in INP's presentation delay; - memory: on devices with little RAM, very large pages risk general slowdowns or the tab being reloaded.
Lighthouse flags an excessive DOM and reports the total number of elements, the maximum depth and the element with the most children: three useful clues about where to intervene.
Reducing nodes at the source
The most effective intervention is not creating useless nodes:
- nested containers: many themes and visual builders wrap each block in four or five purposeless
divs. With CSS Grid and Flexbox the same layout can almost always be achieved with a single level; - hidden content: mobile menus, modal dialogs, inactive tabs and product variants are often in the DOM from the start, just hidden. They can be generated when opened;
- repeated SVG icons: the same SVG pasted a hundred times multiplies the paths; a
useelement pointing to a symbol defined once is far lighter; - pagination or progressive loading: showing a thousand products on one page rarely helps the user.
content-visibility: deferring off-screen work
When content must stay on the page, for example a long article or a documentation page, the CSS property content-visibility lets the browser skip rendering sections that are off screen:
.section {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
With auto, the browser computes layout and paint for a section only when it approaches the visible area. The content stays in the DOM: it can be reached by find-in-page, by screen readers and by anchor navigation.
The second line matters. A skipped section has no known size, and without a hint it would be treated as zero height: the scrollbar would keep changing and content would jump. contain-intrinsic-size provides an estimate; with the auto keyword the browser remembers the real size after the first render and reuses it.
When to use it and when not to
| Situation | Advice |
|---|---|
| Long articles split into sections | Excellent candidate |
| Card lists with similar heights | Good candidate, with an accurate estimate |
| Content at the top of the page | Pointless, it is painted anyway |
| Elements with highly variable height | Possible scroll jumps |
Apply it to blocks of a certain size, not to every single paragraph: on tiny elements the benefit is nil and the risk of jumps grows.
The contain property
Before content-visibility there was already contain, which tells the browser that an element is independent of the rest of the page. With contain: layout paint, for example, a change inside a widget does not force the whole page's layout to be recalculated, and painting does not spill outside its bounds. It is useful for components that update often, such as a clock, a counter or a notifications panel.
Virtualizing long lists
For lists with thousands of rows, such as a data table or an event log, the most radical solution is virtualization: only the visible rows plus a small margin are created in the DOM, and as the user scrolls the same nodes are recycled with new content. The number of nodes stays constant regardless of the list's length.
Virtualization has a price: rows that are not present are not found by the browser's search and need careful handling for accessibility. That is why it is best reserved for application interfaces, while pagination or content-visibility are preferable for editorial content.
Measuring the result
In the DevTools Performance panel, the Recalculate Style and Layout entries show how many elements were involved in each recalculation. Comparing them before and after the change, perhaps on a specific interaction such as opening a menu, makes the gain obvious. In my work, reducing a theme's nesting and generating menus only when opened has often improved INP more than any JavaScript optimization.
In short
- Every DOM node costs in styles, layout, paint and memory.
- Remove useless containers and generate hidden content only when needed.
content-visibility: autowithcontain-intrinsic-sizeskips off-screen rendering.containisolates components that update often.- For huge lists in applications, virtualization keeps the node count constant.