Using CSS containment
CSS containment improves the performance of web pages by allowing the browser to isolate a subtree of the page from the rest of the page. If the browser knows that a part of the page is independent from the rest of the content, rendering can be optimized and performance improved.
The contain
and content-visibility
properties enable developers to inform user agents whether or not an element should render its contents at all, and whether it should render its contents when it is offscreen. The user agent then applies containment to elements when appropriate, potentially deferring layout and rendering until needed.
This guide describes the basic aims of CSS containment and how to leverage contain
and content-visibility
for a better user experience.
Basic example
Web pages often contain multiple sections which are, logically, independent of each other. CSS containment enables them to be treated truly independently from each other when it comes to rendering.
For example, blogs usually contain several articles, each containing a headline and content, as in the markup below.
<h1>My blog</h1>
<article>
<h2>Heading of a nice article</h2>
<p>Content here.</p>
</article>
<article>
<h2>Another heading of another article</h2>
<p>More content here.</p>
</article>
With CSS, we apply the contain
property with a value of content
to each article. The content
value is shorthand for contain: layout paint style
:
article {
contain: content;
}
Logically, each article is independent of the other articles on the page. This information is something that is usually known, and likely quite obvious, to the web developer creating the page. However, browsers don't know the intent of your content and cannot assume that an article or other section of content will be entirely self-contained.
This property provides a way of explaining this to the browser and giving it explicit permission to make performance optimizations. It tells the browser that the internal layout of the element is completely separate from the rest of the page, and that everything about the element is painted inside its bounds. Nothing can visibly overflow.
By setting contain: content
on each <article>
we have indicated this; we have told the browser that each article is independent. The browser can then use this information to make decisions about how to render each <article>
of content. For example, it might not render articles that are outside the viewable area.
When additional articles are appended at the end of the page, the browser does not need to recalculate layout or repaint the preceding content; it also doesn't need to touch any area outside of the containing element's subtree. If box model properties are dependent, however, the browser will need to recalculate layout and repaint. For example, if the <article>
is styled such that its size depends on its contents (e.g. with height: auto
), then the browser will need to account for its size changing.
Key concepts and terminology
contain
values
There are four types of containment: layout, paint, size, and style. Use the contain
property to specify the type or types you want to apply to an element by including any combination of these types.
Layout containment
article {
contain: layout;
}
Layout is normally scoped to the entire document, which means that if you move one element the entire document needs to be treated as if things could have moved anywhere. By using contain: layout
you can tell the browser it only needs to check this element — everything inside the element is scoped to that element and does not affect the rest of the page, with the containing box establishing an independent formatting context.
In addition:
float
layout will be performed independently inside the specified element.- Margins won't collapse across a layout containment boundary.
- The layout container is a containing block for
absolute
- andfixed
-positioned descendants. - The containing box creates a stacking context, therefore
z-index
can be used.
Note: The style
and layout
values of contain
are automatically applied when using the container-type
and container-name
properties.
Paint containment
article {
contain: paint;
}
Paint containment essentially clips the box to the padding edge of the principal box. There can be no visible overflow. The same additional notes are true for paint
containment as layout
containment (see above).
Another advantage is that if the element with containment applied is offscreen, the browser does not need to paint its child elements — these are also offscreen as they are contained completely by that box.
Size containment
article {
contain: size;
}
Size containment does not offer much in the way of performance optimizations when used on its own. However, size containment means that the size of the size-contained element's children cannot affect the size of the element itself — its size is computed as if it had no children.
If you set contain: size
on an element, you need to specify the size of the element using contain-intrinsic-size
, or the longhand properties contain-intrinsic-width
and contain-intrinsic-height
, in that order. If no size is set, the element risks being zero-sized in most cases.
article {
contain: size;
contain-intrinsic-size: 100vw auto;
}
Style containment
article {
contain: style;
}
Despite the name, style containment does not provide scoped styles such as you would get with the Shadow DOM or @scope
.
The main use case for the style
value is to prevent situations where a CSS counter could be changed in an element, which could then affect the rest of the tree.
Using contain: style
ensures the counter-increment
and counter-set
properties create new counters scoped to that subtree only.
You can include more than one containment type by including multiple space-separated values, such as contain: layout paint
or by using one of the two special values.
Special values
There are two special values of contain
that are shorthand for the first three or all four of the containment types:
content
strict
We encountered the first in the example above. Using contain: content
turns on layout
, paint
, and style
containment. As it omits size
, it is a safe value to apply widely.
The contain: strict
declaration, which behaves the same as the declaration contain: size layout paint style
(which includes four space-separated values), provides the most containment. It is riskier to use as it applies size
containment; the risk exists that a box could end up zero-sized due to a reliance on the size of its children.
To remove this risk, always set a size when using strict
:
article {
contain: strict;
contain-intrinsic-size: 80vw auto;
}
The above is the same as:
article {
contain: size layout paint style;
contain-intrinsic-size: 80vw auto;
}
content-visibility
When you have a lot of content that would benefit from heavy containment that will often be offscreen — for example if all your blog posts are viewable on the blog home pages as an infinitely scrollable blog — content-visibility: auto
can be used to apply all of the containments at once.
The content-visibility
property controls whether or not an element renders its contents at all, along with forcing a strong set of containments, allowing user agents to potentially omit large swathes of layout and rendering work until it becomes needed. It enables the user agent to skip an element's rendering work (including layout and painting) until it is needed — which makes the initial page load much faster.
Its possible values are:
visible
: The default behavior — an element's contents are laid out and rendered as normal.hidden
: The element skips its contents. The skipped contents will not be accessible to user agent features such as find-in-page, tab-order navigation, etc., nor be selectable or focusable.auto
: The element turns on layout containment, style containment, and paint containment, as ifcontain: content
was set. If the element is not relevant to the user, it also skips its contents. Unlikehidden
, the skipped content is still available for user interactions, remaining focusable, selectable, in regular tab order, and available to in-content search.
Relevant to the user
User-agents have a concept of content being relevant to the user. An element becomes "relevant to the user" if any of the following are true:
- The element appears in the viewport, or a user-agent-defined margin around the viewport (50% of the viewport dimensions, to give the app time to prepare for when the element visibility changes).
- The element or its contents receive focus.
- The element or its contents are selected, for example by dragging over the text with the mouse cursor or by some other highlight operation.
- The element or its contents are placed in the top layer.
When content-visibility: auto
is set, and the browser determines that content is relevant to the user, the browser will render that content.
Skips its contents
When you set content-visibility: hidden
on an element, you are telling the browser that it is not relevant to the user, and therefore its contents should be skipped and not rendered. This helps to improve performance.
The browser will also skip an element's contents when content-visibility: auto
is set on it, and the browser determines that its content is not relevant to the user.
When an element skips its contents:
- It has layout, style, paint, and size containment turned on.
- Its contents are not painted, as if
visibility: hidden
was set on it. - Its contents do not receive pointer events, as if
pointer-events: none
was set on it.
This happens in both the cases mentioned above, but with content-visibility: auto
the content can be searched, receive focus, and otherwise move from not relevant to relevant. This is not the case for content-visibility: hidden
.
Note: To animate the transition from content-visibility: hidden
to a visible value, you will need to set transition-behavior: allow-discrete
and @starting-style
styles. See transitioning display
and content-visibility
to learn more.
See also
- CSS containment module
- Learn: CSS performance optimization
- CSS container queries
- An Introduction to CSS Containment via Igalia.com (2019)
- The
contentvisibilityautostatechange
event