WSS
BlogPerformanceArchitecturePublished

CSS Containment & Content-Visibility: Rescuing the Main Thread

Rendering massive DOMs destroys your Interaction to Next Paint (INP) score. Here is a deep dive into how to use the contain property and content-visibility: auto to isolate layout and paint calculations, saving precious main thread computation time.

The most expensive operation your browser performs is not executing JavaScript. It is calculating layout.

When you append a single DOM node, resize an image, or toggle a class that changes an element’s dimensions, the browser cannot simply redraw that one element. Because of how CSS flow works, the size of a child can affect the size of its parent, which pushes down a sibling, which expands a container, which triggers a scrollbar, which reflows the entire page.

This cascading layout recalculation is incredibly expensive. On complex pages with thousands of DOM nodes (think long feeds, massive data tables, or single-page applications), a simple interaction can cause the browser to freeze for hundreds of milliseconds while it computes the new geometry of the entire document.

This directly destroys your Interaction to Next Paint (INP) score, dragging down your Core Web Vitals and actively harming your SEO and user retention.

You can fix it with two lines of CSS.

The Problem: Global Scope by Default

CSS is globally scoped by default. The rendering engine assumes that any change anywhere in the document could potentially affect any other element.

If you have a massive footer at the bottom of a 5,000-pixel tall document, and a user expands an accordion in the header, the browser technically evaluates whether that expanding accordion somehow shifts the geometry of the footer. It does this on every frame of an animation.

We need a way to tell the browser: “Stop. This component is completely self-contained. Nothing inside it will affect the outside world, and nothing outside will affect the inside.”

Enter CSS Containment

The contain CSS property allows you to strictly define the scope of an element’s effects on the rest of the document. It provides a way to isolate a subtree of the DOM.

There are four primary types of containment:

  1. contain: layout; Tells the browser that nothing outside this element can affect its internal layout, and nothing inside can affect the layout of external elements. The element becomes a firm, unbreakable box in the document flow.
  2. contain: paint; Tells the browser that children of this element will never be painted outside the bounds of this element. If the element is off-screen, the browser can completely skip painting its children. (This also acts as a containing block for absolute and fixed positioned descendants).
  3. contain: size; Tells the browser that the element’s size can be computed without examining its children. You must provide an explicit width and height.
  4. contain: style; Ensures that properties which can affect more than just an element and its descendants (like CSS Counters) do not escape the element.

For most robust, independent components (like a complex charting widget or an encapsulated comment thread), you can use the shorthand:

.isolated-component {
  contain: strict; /* Equivalent to: layout paint size style */
}

If you don’t know the exact size of the element ahead of time, use:

.content-component {
  contain: content; /* Equivalent to: layout paint style */
}

By adding contain: content to major architectural sections of your page (header, main article, sidebar, footer), you dramatically shrink the scope of the browser’s layout recalculations. When the sidebar changes, the browser knows it only needs to calculate the sidebar, ignoring the massive main article entirely.

The Silver Bullet: content-visibility

CSS Containment is powerful, but it requires careful architectural planning. In 2020, Chromium browsers introduced a property built on top of containment that acts as a near-magical performance toggle: content-visibility.

.long-list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 150px; /* Crucial fallback! */
}

When you apply content-visibility: auto to an element, the browser applies strict containment and completely skips rendering the element and its children if it is off-screen.

It does not calculate layout. It does not calculate paint. It treats the entire DOM subtree as if it had display: none, while retaining the memory of the DOM nodes so they can be instantly rendered the millisecond they approach the viewport.

The contain-intrinsic-size Requirement

If the browser skips rendering an off-screen element, it assigns it a height of 0px.

If you have a list of 1,000 articles and you apply content-visibility: auto to all of them, the 990 articles that are off-screen will suddenly collapse to 0 pixels tall. This completely destroys the browser’s scrollbar calculation. The scrollbar will be tiny, and as you scroll down and elements are rendered, the scrollbar will violently jump and jitter as elements suddenly acquire height.

To fix this, you must provide a contain-intrinsic-size.

This property tells the browser: “While you are skipping the rendering of this off-screen element, pretend it is exactly 150px tall for the purposes of the scrollbar.”

When the element enters the viewport, the browser actually renders it, computes its true height (say, 162px), and smoothly adjusts the scrollbar.

When to use them

Use contain: content on:

  • Major structural page sections (Header, Sidebar, Footer, Main).
  • Third-party widgets (Ads, embedded tweets) to prevent them from causing layout shifts.
  • Any heavily animated component.

Use content-visibility: auto on:

  • Long lists of complex items (e.g., social media feeds, massive tables, comment threads).
  • Footer sections on incredibly long pages.
  • Hidden sections of an application (like inactive tabs in a dashboard).

Conclusion

The era of shipping tens of thousands of DOM nodes without consequence is over. Core Web Vitals, and specifically INP, demand a ruthless approach to main-thread execution.

By strategically applying CSS Containment and content-visibility, you can decouple the rendering performance of your application from the size of your DOM, delivering 60fps scrolling and instant interactions on even the most complex web applications.

Related posts