content-visibility
is a CSS property that controls whether or not an element renders its contents. It enables the browser to bypass layout and rendering work for elements not immediately needed by a user. This can make the initial page load faster.
The content-visibility
property is part of the CSS Containment Specification, whose objective is to improve performance.
In this article, we will dive into the benefits and practical use cases of the content-visibility
CSS property, as well as situations where it might not be the most suitable option. But first, we’ll lay the groundwork with an exploration of the fundamentals of rendering.
Rendering is the process of transforming the code of a webpage into pixels that a user can see in the web browser.
When you enter a URL in the address bar of a browser and fire off a request for a webpage, a series of events are kicked off. There are some technical background processes, but the main event is to download the resources that make up the webpage, such as the HTML file, font files, and CSS files.
The part of the process that relates most to our discussion of rendering performance is what happens after the HTML file has been loaded in the browser. Google refers to this process as the rendering pixel pipeline. The areas involved are:
<body>
element typically affects its children’s widths all the way down the tree, a process that can be quite intensiveIt’s important to understand that the rendering pixel pipeline is a transformative series of operations — the result of the previous operation is carried into the next operation to create new data. Updating the rendering pipeline is costly, so if we can skip an area, it can have a positive impact on performance.
Google has color-coded and categorized the areas as follows:
This facet of categorization is significant because this is how they are visually represented in the Performance tab of Chrome’s DevTools. You can see the matching color encoding in the summary pane below:
DevTools can be used to tell us if we have realized performance gains. We can compare recorded sessions of a page’s performance before and after changes have been made. If you are unsure how to profile performance, you can consult the docs on analyzing runtime performance.
If you read articles from Google on content-visibility
, you might find that the term “rendering” is used in different contexts. Some tutorials may be talking about the rendering of a webpage, or the category that you see in Chrome’s DevTools that includes the style and layout areas.
Overloading the term like this can be a source of confusion. In this tutorial, we will follow the definition of rendering that we introduced at the start of this section.
The goal of the CSS Containment Specification is to improve the performance of webpages by allowing the browser to isolate a portion of the page (DOM subtree) from the rest of the page. Containment enables much more powerful optimizations by browsers because it limits how widely a given change can affect a document.
The reason that developers have to intervene is that the browser can’t accurately guess by itself if parts of a page are independent. The specification defines the contain
and content-visibility
properties to give developers the capability to identify isolated parts of the page.
There are four types of containment:
width: 100px
and aspect-ratio: 1/1
and overflow: hidden
, its descendants will not influence its size, so we don’t need to examine themcounter-increment
, counter-set
, and content
properties must be scoped to the container. This allows us to potentially skip style computation for the descendants if all we need is to compute styles on other elementsThe contain
property tells the browser what type of containment an element has. It may be hard to figure out which containment values to use for different use cases. But this article won’t dive any deeper into the contain
property — we’ll be looking at content-visibility
, which can apply containment to an element automatically. It ensures that you get the largest performance gains the browser can provide with minimal effort from you as a developer.
content-visibility
?The content-visibility
property enables the browser to skip an element’s layout and painting until it is needed, which makes the initial page load faster. Because the browser does not have to re-render the DOM or page layout as often, this can result in performance benefits over the entire lifecycle of a page or web app.
content-visibility
drawbacksTo reap the benefits of the content-visibility
property, you may need to reorganize your HTML into sections. This may not be a big deal for you, but it may be more work than anticipated if some styles are broken by restructuring your page. Just keep in mind that it is not the case that simply adding the content-visibility
property to any page will magically improve performance.
Additionally, the content-visibility
property may introduce scrollbar jumping when sections come into the viewport and are fully rendered. The height of an element outside of the viewport may be zero or may have an estimated size applied to it through the contain-intrinsic-size
property. When the styles are applied to an element as it comes into view, it may grow or shrink the element, which will resize the page and affect the scrollbar length.
From my exploration, you can incur a performance penalty by applying content-visibility
to a section that is relevant to the user (on-screen, focused, or selected), similar to lazy loading with the loading
attribute in HTML.
As a result, you may need to tweak styles to ensure the browser considers something off-screen. The CSS Containment Spec says that an element is onscreen if the “overflow clip edge intersects with the viewport, or a browser-defined margin around the viewport.”
It is not clear what the value of the browser-defined margin is — there is a possibility that something may be off-screen to the user, but the browser considers it on-screen because of this fine, unknown margin! However, this is literally an edge case.
One of the features of content-visibility: auto
is that off-screen content is available in the DOM and the accessibility tree. The flip side of this is that landmark elements with style features such as display: none
or visibility: hidden
will also appear in the accessibility tree when off-screen because the browser won’t render these styles until they enter the viewport. To prevent these from being visible in the accessibility tree as you may expect, you will need to manually add aria-hidden="true"
.
It is good to keep in mind that CSS Containment is a new concept and people are unlikely to know how to use it. In the State of CSS 2023 survey reading list, it was the third most featured item. We can deduce from this that there is awareness of the property, but people haven’t gotten around to learning it yet!
During the maintenance of a codebase in a team, people can alter the HTML of a page unaware that their change has affected containment conditions, which undoes the performance gains accrued from using content-visibility
. After all, it is a new concept, so it won’t always be front of mind for people to check.
content-visibility
in a projectPerformance is a tricky topic because it often involves tradeoffs. It is becoming more common to set a performance budget to be explicit about the decisions you have made and the rationale you followed. Ideally, you will be confident that you’ll achieve your performance goals before you build something, but none of us have a crystal ball!
As you build something for the web, you may find that you are falling below your desired performance thresholds. Is this the time to reach for content-visibility
? Or should you be using it as a common practice and factor the savings in beforehand?
It is difficult to draw a general conclusion here because this is new territory for CSS. The question is: do we treat content-visibility
as a performance optimization technique or do we treat it as a common practice?
In the optimization camp, you don’t use the property until you need it. Otherwise, you’re in premature optimization territory. The trouble there is that you won’t actually notice the poor performance unless you are actively testing on the lowest-specced devices available.
In the common practice camp, you could use content-visibility
almost everywhere. Potentially any page could benefit from using the property when you have content that is off-screen, but the payoff will vary. You will need to draw a line somewhere to be practical. You may want to focus on larger pages where the payoff is greater.
In any case, you should be considerate of how you use the content-visibility
property. If there is a lot of churn in the layout or contents of a page, it is probably better to defer using content-visibility
until that part is in a more stable state. You need to be sure something is off-screen and will be targeted correctly.
I think that this would be easier to manage in a component-driven frontend where the encapsulation of fragments of a page is more explicit. Containment and web components are complementary in their pursuit of identifying independent pieces of a webpage. Conversely, if your content is written in Markdown, it is likely that your document has fewer identifiable sections and it is more challenging to use content-visibility
.
content-visibility
The content-visibility
property accepts one of three values:
visible
: An element’s contents are laid out and rendered as normal. This is the defaultauto
: The element turns on layout containment, style containment, and paint containment. If the element is not relevant to the user, it also skips its contents. The skipped contents are accessible to the browserhidden
: The element skips its contents. The skipped contents are not accessible to browser-affected elements and are not available to browser features such as find-in-page, and tab order navigationYou may need to use the contain-intrinsic-size
property alongside content-visibility
to realize performance gains associated with size containment.
contain-intrinsic-size
To realize the potential benefits of content-visibility
, the browser needs to apply size containment. Size containment allows the browser to lay out an element as though it has a fixed size, preventing unnecessary reflows by avoiding the re-rendering of child elements to determine the actual size.
By default, size containment treats elements as though they have no content, and may collapse the layout in the same way as if the contents had no width or height. This means that the element will lay out as if it were empty. The contain-intrinsic-size
property allows developers to specify an appropriate value to be used as the size for layout.
If you are not sure of the exact dimensions of an element, there is an auto
keyword for contain-intrinsic-size
that can help. The auto <length>
value allows you to supply a placeholder value as the second number.
For example, if you specified contain-intrinsic-size: auto 500px
, the element will start out with a 500px intrinsic width and height. When the element comes into view and its contents are rendered, it will change to the actual rendered intrinsic size:
.offscreen-section{ content-visibility: auto; contain-intrinsic-size: auto 500px; }
Any subsequent rendering size changes will also be remembered. In practice, this means that if you scroll an element with content-visibility: auto
applied, and then scroll it back off-screen, it will automatically retain its ideal width and height, and not revert to the placeholder sizing.
content-visibility: hidden
Applying content-visibility: hidden
to an element keeps the contents unrendered regardless of whether or not it is on-screen. It is up to you to change the value to visible
in order for the user to see it when needed.
For me, the use cases for this are not super clear. Perhaps you can accrue benefits in UI patterns that involve revealing contents such as disclosure widgets and hidden nav menus.
I could see single-page applications (SPAs) benefitting from it, where inactive app views can be left in the DOM with content-visibility: hidden
applied to them cached with the rest of the page. This can make a view quick to render when it is activated again.
Would you use content-visibility: hidden
over display:none
? Let’s compare it to other common ways of hiding an element’s contents to evaluate the differences:
display: none
: Hides the element and destroys its rendering state. Unhiding the element is expensive because it renders a new element with the same contentsvisibility: hidden
: Hides the element and keeps its rendering state. It still occupies space on the page and can be clicked on. It also updates the rendering state any time it is needed, even when hiddenI want to explore how the content-visibility
property can be applied to different sections of a typical webpage and demonstrate what the impact on performance is. I will explore two different use cases in the subsequent subsections:
The example is a landing page for the artist Angèle, coded by Rafaela Lucas. It is of typical size with five major sections (hero, tour dates, videos, album, follow):
I will audit the desktop performance for these scenarios to understand the outcomes. I ran a performance audit to establish a baseline, which I refer to as the default. You can see how I did that in the video below using the Chrome DevTools incognito mode:
You can find the code for this example in this GitHub repo, where I also explored scenarios and examples not discussed here.
content-visibility
to a hidden sectionThe website navigation section (main navigation) is hidden by default. You must click on the hamburger menu to open it:
First, I added content-visibility:auto;
to the menu and found that the performance was worse (see scenario A2 in table below). I was a bit surprised:
.menu { content-visibility:auto; }
Then, I added content-intrinsic-size
. Because the menu has a fixed size (100vw by 100vh), I could provide the exact dimensions. This pushed the performance in the other direction and resulted in a small improvement in both rendering and painting (see scenario A3 in the table below):
.menu { content-visibility:auto; content-intrinsic-size: 100vw 100vh; }
Finally, using content-visibility:hidden
was worse than scenario A3 (see scenario A4 in table below):
.menu { content-visibility: hidden; }
Based on these scenarios, we can conclude that you can achieve a small gain in performance when you use content-visibility:auto
along with content-intrinsic-size
on a hidden element like this. However, it is probably not worth pursuing this type of marginal gain unless you want things really optimized.
Here is a table of the auditing results:
Name | Scenario | Loading | Scripting | Rendering | Painting | System | Idle | Total |
---|---|---|---|---|---|---|---|---|
A1 | Default | 21 | 6 | 114 | 30 | 178 | 4608 | 4957 |
A2 | Nav main menu has content-visibility:auto applied to it |
34 | 7 | 181 | 82 | 212 | 4479 | 4995 |
A3 | Nav main menu with content-visibility:auto and content-intrinsic-size: 100vw 100vh; specified. | 22 | 6 | 106 | 22 | 128 | 4715 | 4999 |
A4 | Nav main menu with content-visibility:hidden specified. |
30 | 7 | 144 | 21 | 155 | 4752 | 5109 |
content-visibility
to off-screen sectionsThere are three major sections that are initially off-screen. Let’s apply content-visibility:auto
to them:
.videos, .album, .follow { content-visibility: auto; }
There was a significant improvement in rendering and painting of about 40% (see scenario B2 in the table below).
When I added contain-intrinsic-size
, the performance also improved significantly (see scenario B2 in the table below). However, it was marginally worse than scenario B2. It would be better to omit contain-intrinsic-size
in this case. This contrasts with results from scenarios A2 and A3.
The summary of the auditing results is below:
Name | Scenario | Loading | Scripting | Rendering | Painting | System | Idle | Total |
---|---|---|---|---|---|---|---|---|
B1 | Default | 21 | 6 | 114 | 30 | 178 | 4608 | 4957 |
B2 | Lower 3 sections have content-visibility:auto applied. |
19 | 5 | 61 | 11 | 145 | 4744 | 4985 |
B3 | Lower 3 sections have content-visibility:auto and contain-intrinsic-size specified. |
23 | 5 | 64 | 16 | 137 | 4673 | 4918 |
content-visibility
ready for general usage?At the time of writing, the content-visibility
property is only available in Chrome, Edge, and Opera.
It is in Firefox behind a flag. The CSS Containment Specification is slated to be adopted by all major browsers as a focus area of Interop 2023.
There are cases where you can treat usage as a progressive enhancement. I could not find a polyfill, so you may have to skip it if this is essential for you.
There are plenty of existing techniques to boost web performance. You will need to evaluate if content-visibility
is complementary or not with the other techniques you deploy. Here are a couple that come to mind.
content-visibility
with lazy loadingLazy loading does not request the resource (img
or iframe
) until it is needed. However, for content-visibility: auto
, the browser will still request the data, but it just won’t render it.
The number of network requests and the volume of data sent over the wire are two of the most significant factors affecting page loading speed. In this regard, lazy loading would usually offer bigger performance gains than adding content-visibility: auto
to some sections.
In theory, you can use both. However, I haven’t seen usage of this in the wild, so I can’t say if you would encounter any issues.
content-visibility
with React VirtualizedReact Virtualized is a library for efficiently rendering large lists and tabular data. This library presents only the required rows and indicates the presence of other hidden rows via CSS styles. It manipulates the DOM elements to remove past elements and add new elements that come into view.
React Virtualized only loads the data that is needed, whereas content-visibility: auto
loads all of the data but skips rendering. These are not complementary techniques when applied to the same elements.
The content-visibility
property and CSS containment offer valuable performance-boosting power to CSS. For the landing page example I explored, I was able to reduce rendering and layout by approximately 40 percent.
On the surface, usage looks straightforward — you get performance gains by using just two properties! However, it is challenging to apply them correctly to your code if you know little about web performance. You can hamper performance if you aren’t careful.
Using content-visibility
comes with its own set of considerations and potential challenges. First, it might require you to restructure your content into distinct sections to prevent issues like scrollbar inconsistencies as content becomes visible on the screen. Additionally, there are accessibility concerns that need to be addressed. I realized that it’s crucial to carefully monitor performance, as the results were often not what I was expecting.
Embarking on this journey brings about various questions about the division of responsibilities within a team. As the knowledge required for frontend development continues to expand, who should take the lead on this — is it the UI engineer, aka “the CSS guy”? Or should an architect or a performance specialist be responsible?
As our product continues to grow and evolve, we must establish a method to regularly review these properties to ensure they are indeed optimizing — rather than hindering — our performance.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.