Performance

Field Testing CSS Containment for Web Performance Optimization

Brian Louis Ramirez
June 18, 2024
7 min read
Contact our team
Let's check your website speed.
Contact sales
Share blog post
Everyone benefits from speed.
https://speedkit.com/blog/field-testing-css-containment-for-web-performance-optimization

In an earlier analysis of real user data, we found that category or listing pages on e-commerce sites have the worst responsiveness as measured by Interaction to Next Paint (INP). So we asked ourselves:

Can we use CSS containment to improve rendering and interactivity? If so, by how much?

In this post, we present the results from a series of A/B tests we ran to answer that question.

What is CSS Containment?

CSS containment is a concept that enables developers to optimize rendering performance by explicitly letting the browser know how to handle the rendering of certain nodes or elements - or whether to skip rendering of those elements altogether.

Containment can be applied to an element’s styling, layout, paint, size, or any combination of those properties. For example, a given page might have several article elements, each with a complex structure and layout.

<main>
  <article><!-- Lots of child nodes --><article>
  <article><!-- Lots of child nodes --><article>
  <article><!-- Lots of child nodes --><article>
</main>


The contain property has been a baseline CSS feature since 2023, and as MDN explains, it can be used to optimize rendering performance:

Descendants of the element don't display outside its bounds. If the containing box is offscreen, the browser does not need to paint its contained elements - these must also be offscreen as they are contained completely by that box.

So by applying contain: content to the article elements in CSS, their layout, paint and styling (or a combination of those) can be contained so as not to affect the rest of the page. If an article is offscreen, it will not be painted.

article {
  /* The value 'content' is shorthand for 'layout paint style' */
  contain: content;
}


The content-visibility property and auto value (supported in most major browsers except Safari at the time of writing) allows us to tell the browser which elements to “lazy-render” while keeping the content accessible. And by setting contain-intrinsic-size or contain-intrinsic-height, we can tell the browser how much vertical space to reserve before rendering the contained elements so the scrollbar doesn’t get jittery.

article {
  contain: content;

  /* For Chrome, Samsung Internet, Edge and Firefox */
  content-visibility: auto;
  /* Reserves vertical space */
  contain-intrinsic-height: auto 76vw;
}


A web.dev article explains why the property can be beneficial:

Because rendering is skipped, if a large portion of your content is off-screen, leveraging the content-visibility property makes the initial user load much faster. It also allows for faster interactions with the on-screen content.

So just how much can both contain and content-visibility improve performance?

Testing CSS Containment Locally

We can see the potential benefits of CSS containment in the following examples.

Baseline

To start, we can load this demo page. It has 1,000 section elements, and each section includes 20 child div elements, so it can be considered a fairly complex DOM. Each div has a group number and a unique background color. None of these elements have the properties contain: content or content-visibility: auto applied.

<!-- 1000 of the following 'container' divs -->
<section class="container">
  <!-- 20 of the following 'box' divs -->
  <div
    class="box"
    style="background-color: hsl(3deg, 100%, 50%)"
  >
    1
  </div>
</section>

.box {
  padding: 0 1rem;
  height: 1.5rem;
}

.container {
  color: white;
  margin: 1rem 0;
  display: flex;
  flex-wrap: wrap;
}
Demo page with lots of content


A performance trace in Chrome 125 with 6x CPU throttling on an Apple M2 Macbook Air shows that rendering work kept the main thread busy for 825 ms.

Performance Trace in Chrome DevTools showing 825 ms of rendering work (in purple) without CSS containment applied to content groups

Test: Containment of Content Groups

Now we apply CSS containment to each content group on this demo page.

.container {
  contain: content;
  content-visibility: auto;
  contain-intrinsic-height: auto 6rem;
}


We see that for an initial rendering of the page, only 172 ms of rendering work is done - a reduction of ~80%.

If you use new or existing Chrome profile windows, the rendering times shown in performance traces and summaries using might not be reliable (we filed a bug report). However, they did appear to be more accurate in Guest windows.
Performance Trace in Chrome DevTools showing 172 ms of rendering work (in purple) with CSS containment applied to content groups

Test: Containment of Site Sections

Alternatively, instead of applying containment to each content group, we could apply it to entire site sections (i.e. groups of groups). But the biggest reductions in initial rendering time appear to come from applying containment to smaller groups.

Applying CSS containment to each content group appears to reduce the amount of rendering work more than if we apply it to larger groupings

Test: Containment and Interactivity

CSS containment can also improve interactivity. For example in this demo, if we add 1,000 groups of elements to the DOM upon interaction without contain: content and content-visibility: auto, we see a long purple bar in the flame chart showing “Layout” work.

A performance trace of adding content to the DOM on click. When the content is styled with CSS containment, rendering work is around 54 ms. Without CSS containment, rendering work is around 732 ms.


The above local tests of prototyped pages shows a reduction in rendering times from CSS containment. So how does that translate to real-world scenarios?

Testing CSS Containment in the Field

Hypothesis

Applying CSS containment to large, complex, below-the-fold elements improves INP while not affecting other performance metrics.

Method

In a series of 50/50-split A/B tests using Speed Kit, we tested CSS containment on three types of webpages on two different websites:

A large sporting goods retailer:

  • Server-side-rendered category pages
  • Server-side-rendered product detail pages

A small clothing retailer:

  • Client-side-rendered category pages

In the test group, we added CSS containment to product tiles or site sections outside of the initial viewport, targeting smaller screens where we were most likely to see performance changes.

Test #1: Server-Side-Rendered Category Pages

We noticed that product category pages on a large sporting goods retailer’s site had high INP times.

In the case of this e-retailer, category pages appeared to be visually complex with:

  • 36 server-side-rendered (SSR) product tiles on the category pages with the most traffic
  • each product tile composed of an image slider and variant selector, and having up to 130 descendant elements for a total node depth of 6
Example server-side-rendered product tile


Our hypothesis was that CSS containment could help to improve INP and rendering times on mobile devices by reducing the amount of rendering work the browser needs to do initially, and upon filtering products on the page.

So we A/B tested (total n = 813,323; target split: 50/50) the page type, applying the following style declarations to product tiles on mobile devices in the test group:

@media screen and (max-width: 899px) {
  .product-tiles:nth-of-type(n + 3) {
    contain: content;
    content-visibility: auto;
    contain-intrinsic-height: auto 418px;
  }
}


Looking at the top three mobile browsers with the most traffic, we found that at the 75th percentile (i.e. the slowest 25%):

  • The paint metrics First Contentful Paint (FCP) and Largest Contentful Paint (LCP) didn’t change much
  • Interaction to Next Paint (INP) improved by ~120 ms on Samsung Browser
  • Cumulative Layout Shift (CLS) regressions of ~0.09 in mobile Chrome and ~0.02 in mobile Samsung Browser
Green, negative values = improvement; red, negative values = regression. Safari doesn’t support the metrics LCP, INP and CLS.


That leaves us with some interesting follow-up questions:

  • Why did INP improve so dramatically for mobile Samsung Browser users? Is it because they have slower devices?
  • Why did CSS containment cause the layout to shift more? Could that be due to differing content on category pages?

Test #2: Client-Side-Rendered Category Pages

We also A/B-tested CSS containment on a small clothing retailer’s category pages that also had high INP. In this case, we applied containment to client-side-rendered product tiles.

Category pages on this site appeared to be visually complex with:

  • 30 client-side-rendered (CSR) product tiles per page initially
  • “infinite scrolling”, which renders an additional 30 CSR product tiles
  • each product tile composed of an image slider and color selector, and having up to 106 descendant elements for a total node depth of 14
Example client-side-rendered product tile


In this test (total n = 209,012), we applied the following CSS to product tiles on screens where the page layout had rows of two product tiles:

@media screen and (max-width: 767px) {
  .listing--wrapper .product--box:nth-of-type(n + 6) {
    contain: content;
    content-visibility: auto;
    /* In this case, tiles scale proportionally, 
       so we use a relative unit as a placeholder. */
    contain-intrinsic-height: auto 76vw;
  }
}


At the 75th percentile, the most notable performance differences were:

  • a 148 ms LCP improvement in mobile Samsung Browser
  • a  27 ms INP improvement in mobile Chrome
  • a 47 ms faster INP and ~0.1 less CLS in Samsung Browser
Green, negative values = improvement; red, negative values = regression. Safari doesn’t support the metrics LCP, INP and CLS.


Surely, a ~47 ms INP improvement at the 75th percentile is nothing to scoff at. But we are still left with several questions:

  • Why did INP and CLS improve with CSS containment on these client-side-rendered category pages?
  • Why did CLS improve much more so in mobile Samsung Browser compared to mobile Chrome?
  • Why did the metrics change differently compared to the first test?

Test #3: Server-Side-Rendered Product Detail Pages

We also ran an A/B test on a product detail page (the same sporting goods site from Test #1) since it also had sub-optimal INP.

The page type can be considered visually complex due to:

  • excessive DOM size (1,500+ elements)
  • excessive length (17,000+ px in height)
  • numerous content blocks with varying height depending on content
  • numerous third-party widgets (e.g. video players, product ratings, coupons)
Wireframe of a product detail page showing that CSS containment was applied to sections below the fold


We applied CSS containment to sections that we identified on multiple product pages.

@media screen and (max-width: 899px) {
  .product-slider-wrapper {
    contain: content;
    content-visibility: auto;
    contain-intrinsic-height: auto 344px;
  }

  .recommendations-wrapper {
    contain: content;
    content-visibility: auto;
    contain-intrinsic-height: auto 1000px;
  }

  .footer {
    contain: content;
    content-visibility: auto;
    contain-intrinsic-height: auto 858px;
  }
}


In this test (total n = 1,154,365), when we look at the data for the 75th percentile, we see no big improvements nor regressions due to CSS containment.

Green, negative values = improvement; red, negative values = regression. Safari doesn’t support the metrics LCP, INP and CLS.


That leaves us with the question: did we not apply CSS containment to enough complex elements?

Conclusion: Should you use CSS Containment?

Our field tests revealed that CSS containment did not affect performance metrics in equal measure on mobile devices for each of the test pages and sites at the 75th percentile:

  • There were some INP improvements and CLS regressions on server-side-rendered category pages.
  • There were some INP and CLS improvements on client-side-rendered category pages. That coincided with our local tests showing INP improvements from using CSS containment.
  • Metrics on server-side-rendered product pages didn’t change much.
  • FCP in Safari didn’t change much. But iPhones do typically have faster CPUs than their Android counterparts.

Some metric changes were more substantial at the higher percentiles (e.g. a 245 ms INP improvement in mobile Samsung Browser at the 95th percentile). Still, we only tested on two different websites on a total of three different test pages using very similar CSS. Further research and testing across more websites, tech stacks and web development scenarios is needed to draw definitive conclusions.

So should you add CSS containment to your web perf toolbox? It can definitely come in handy, but it is not a one-size-fits-all solution. If you add CSS containment to your web pages, you should consider testing with real user data.

A big thanks goes out to Andrea Verlicchi and Florian Bücklers for reviewing this post!

GET STARTED

Book a free website speed check

We analyze your website speed, identify web vitals issues, and compare your competitors.

Book free speed check
iPhone 15 Device CheckLaser Scanner