CSSCSS Containment (contain)

CSS Containment (contain)

Rendering a page is more than "apply styles once." When something changes — content updates, an element resizes — the browser has to figure out how far that change ripples: does it affect just this element, its subtree, or potentially the entire page? The contain property lets you tell the browser, explicitly, "whatever happens inside this box, stays inside this box," which lets it skip work outside the boundary entirely.

The four containment types

Value

What it isolates

layout

The element becomes an independent layout root — its internal layout cannot affect, and is not affected by, layout outside it

paint

Descendants can't paint outside the element's bounds; also implies clipping, similar to overflow: hidden for rendering purposes

size

The element's size is computed without looking at its children's content at all — must be paired with an explicit size

style

Certain style effects (like counters) are scoped so they cannot leak in or out of the element

CSS
.widget {
  contain: layout paint; /* the two most commonly combined together */
}

/* Shorthand for common combinations */
.widget-a {
  contain: content; /* layout + paint + style */
}

.widget-b {
  contain: strict; /* layout + paint + style + size (needs explicit size) */
}
Why this matters for performance

Without containment, a layout change anywhere in the DOM can, in the worst case, force the browser to re-check layout for the entire page — because technically an ancestor's size could depend on any descendant. Containment lets the browser prove that a subtree is self-contained, so it only needs to redo layout/paint work inside that boundary.

CSS
/* Without containment: a single card resizing (e.g. new content loads)
   could, in theory, ripple layout calculations through ancestors and
   siblings, because nothing tells the browser otherwise. */
.card {
  padding: 16px;
}

/* With containment: the browser can safely assume this card's internal
   changes never affect anything outside its box. */
.card {
  padding: 16px;
  contain: layout paint;
}
contain: size needs a real size
contain: size tells the browser to size the element as if it had no content at all — which means the element collapses to zero size unless you also give it an explicit width/height (or use aspect-ratio). It is rarely used alone for this reason; it usually shows up bundled inside content-visibility: auto instead.
Relationship to content-visibility

content-visibility: auto is the practical, high-impact use of containment — it automatically applies layout, style, and paint containment to off-screen content, and additionally skips rendering that content's subtree entirely until it scrolls into view.

CSS
.long-article section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* placeholder size while off-screen */
}
Reach for content-visibility first
For most real performance wins (long feeds, articles, off-screen sections), content-visibility: auto gets you the benefit of containment automatically, with less manual tuning than applying contain by hand everywhere. Use bare contain when you specifically need isolation without the "skip rendering off-screen" behavior.
When browsers apply containment automatically
  • Certain elements get implicit containment from the spec, without any CSS — for example contain: content behavior is implied on elements like <details> in some engines

  • content-visibility: auto applies containment automatically to any element it is set on

  • Explicit contain is still the only way to get containment on an arbitrary element you control

Containment for widgets and components

Independent, self-contained UI widgets are the ideal candidate for containment — a chat widget, a map embed, a comment thread — anything whose internal changes genuinely have no business affecting the rest of the page.

CSS
.chat-widget {
  contain: layout style paint;
  /* Internal re-renders (new messages arriving, typing indicators)
     never force the browser to re-check layout outside this box */
}
Containment changes behavior, not just performance
contain: paint (and the shorthands that include it) clips overflowing content and establishes a new containing block for absolutely/fixed positioned descendants — the same side effects as overflow: hidden plus a positioning context. Test tooltips, dropdowns, and anything relying on escaping the box's bounds before applying it broadly.
Measuring the benefit
  • Open DevTools Performance panel, record a scroll or an interaction that updates content

  • Compare the "Layout" and "Recalculate Style" time before and after adding contain

  • On a long list/page with many independent widgets, the difference tends to show up as far fewer, much smaller layout passes

contain vs will-change

Both properties are performance hints, and both create side effects (new stacking/containing contexts), but they solve different problems. will-change tells the browser to prepare for an upcoming change (usually ahead of an animation); contain tells the browser an element's internal changes never escape its box.

Property

What it optimizes

Typical use

contain

Isolates layout/paint/style so changes inside never ripple outside

Independent widgets, list items, self-contained components

will-change

Pre-promotes an element to its own compositor layer ahead of an animation

An element about to animate transform or opacity

Do not combine them by default
Applying both broadly "just in case" creates unnecessary compositor layers and containing contexts across a page, which can itself hurt memory and performance. Reach for each only where you have a specific, identified case that benefits from it.
Containment and tables

Table layout is one place where contain: layout has a visible, sometimes surprising effect: since a contained element becomes an independent layout root, applying containment to a table cell's content can change how intrinsic sizing interacts with the rest of the table.

CSS
.table-cell-content {
  contain: layout;
  /* This cell's content no longer influences sibling cell sizing in the
     way it otherwise would -- verify actual table alignment after
     adding containment to any cell content */
}
A before/after measuring example
  1. Build (or find) a page with many repeated, independent widgets — e.g. 50 dashboard cards, each with its own internal state

  2. Open DevTools Performance panel, record while triggering an update to one card (a counter incrementing, a badge changing)

  3. Note the "Recalculate Style" and "Layout" durations in the recorded trace

  4. Add contain: layout style paint to the card component

  5. Repeat the same recorded interaction and compare the same two metrics

  6. On a page with many siblings, the layout/style recalculation scope should shrink noticeably, since the browser can now prove the change cannot affect anything outside the card

Browser support

Value

Support level

contain: layout

Broadly supported in evergreen browsers

contain: paint

Broadly supported in evergreen browsers

contain: style

Broadly supported, effect is narrower (mainly counters)

contain: size

Broadly supported, but rarely useful on its own

content-visibility

Chromium and Firefox; historically slower in Safari

contain degrades safely
An unsupported contain value is simply ignored by older engines — the element renders exactly as it would without containment, just without the performance benefit. There is no visual fallback to design for, which makes it low-risk to adopt even before checking Baseline status carefully.
Quick recipe for a typical component library

CSS
/* A sensible default for most independent, self-contained widgets:
   layout + paint containment, without size (which needs an explicit
   size and is easy to get wrong). */
.widget {
  contain: layout paint;
}

/* Reach for content-visibility instead when the widget is also
   frequently off-screen (long lists, feeds): */
.feed-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 300px;
}
Next
See the biggest practical payoff of containment in content-visibility & intrinsic-size.