EdgeCases Logo
Feb 2026
CSS
Surface
6 min read

CSS contain: Render Isolation for Performance

Tell browsers 'this element is independent' with CSS contain. Layout, paint, and style calculations get scoped to the subtree—potentially cutting layout costs by 80%+.

css
contain
performance
rendering
optimization
layout
paint

Change one element's width, and the browser recalculates layout for the entire page. The contain property tells browsers "this element's internals won't affect anything outside"—unlocking significant performance gains by limiting recalculation scope.

The Problem: Global Recalculation

Every DOM change potentially triggers style recalculation, layout (reflow), and paint for the entire document. When you update a single div's content, the browser must verify that nothing else shifted as a result—even elements completely unrelated to your change.

// Adding text to one element...
document.querySelector('.widget').textContent = 'Updated!';

// ...might cause:
// 1. Style recalc for ancestors (inheritance check)
// 2. Layout for siblings (did anything shift?)
// 3. Paint for unrelated elements (composite layers)
// 4. Counter/list recalculation throughout document

CSS Containment: Scope Boundaries

The contain property creates optimization boundaries. It's a promise to the browser: "I guarantee this element is independent—skip checking the rest."

.widget {
  contain: layout;  /* Layout changes won't escape */
}

.card {
  contain: paint;   /* Paints are clipped to this element */
}

.isolated {
  contain: strict;  /* Maximum isolation */
}

Containment Types

Layout Containment

.widget {
  contain: layout;
}

What it does: Internal layout changes won't affect external elements. The element becomes a containing block for absolutely positioned descendants and establishes an independent formatting context.

  • Creates a new stacking context
  • Becomes a containing block for position: absolute children
  • Establishes independent formatting context (like display: flow-root)
  • Internal float/margin changes don't affect siblings
/* Without layout containment */
.sidebar {
  float: left;
}
.main {
  /* Layout depends on .sidebar's width */
}

/* With layout containment */
.sidebar {
  contain: layout;
  float: left;
}
.main {
  /* Browser knows .sidebar won't affect .main's layout */
}

Paint Containment

.widget {
  contain: paint;
}

What it does: Content is clipped to the element's bounds. Descendants can't paint outside this box, enabling paint optimizations.

  • Acts like overflow: hidden for painting (but not scrolling)
  • Creates a new stacking context
  • Creates a containing block for positioned elements
  • Off-screen elements can skip paint entirely
/* Useful for virtualized lists */
.list-item {
  contain: paint;
  /* Browser can skip painting items outside viewport */
}

Style Containment

.widget {
  contain: style;
}

What it does: Counter and quote styles are scoped to this subtree. Less commonly used, but important for components using CSS counters.

/* Counters are scoped */
.component {
  contain: style;
  counter-reset: items;
}

.component li::before {
  counter-increment: items;
  content: counter(items) ". ";
  /* Won't affect or be affected by counters outside */
}

Size Containment

.widget {
  contain: size;
  width: 200px;
  height: 150px;
}

What it does: Element's size is determined without looking at children. Requires explicit sizing—without it, the element collapses to 0×0.

  • Danger: Element ignores children for sizing completely
  • Must set explicit width and height
  • Useful when size is known ahead of time (fixed cards, ads)
/* ❌ BAD: Element collapses to 0×0 */
.card {
  contain: size;
  /* No explicit size = invisible! */
}

/* ✅ GOOD: Explicit dimensions */
.card {
  contain: size;
  width: 300px;
  height: 200px;
}

Shorthand Values

/* content = layout + paint + style */
.widget {
  contain: content;
}

/* strict = layout + paint + style + size */
.widget {
  contain: strict;
  width: 300px;   /* Required with size! */
  height: 200px;
}

contain: content is the safest "set and forget" value—it provides most benefits without requiring explicit sizing.

Real Performance Impact

Containment shines with many independent components updating simultaneously:

/* 100 widgets updating every second */
.dashboard-widget {
  contain: content;
}

/* Before: Each update → full document layout check
   After:  Each update → only widget subtree checked

   On a dashboard with 100 widgets:
   - Without contain: ~15ms layout per update
   - With contain: ~2ms layout per update */

When It Helps Most

  • Virtualized lists: Items entering/leaving view
  • Dashboards: Multiple widgets updating independently
  • Chat/feed UIs: Messages being added frequently
  • Animations: Elements that animate without affecting siblings
  • Third-party embeds: Isolate unpredictable content

Gotchas and Edge Cases

1. Overflow and Paint Containment

/* Paint containment clips content! */
.container {
  contain: paint;
}

.tooltip {
  position: absolute;
  top: -50px;  /* This will be clipped! */
}

Paint containment acts like overflow: clip. Absolutely positioned descendants that escape the element's bounds will be clipped.

2. Size Containment and Flexbox/Grid

/* Size containment breaks intrinsic sizing */
.flex-item {
  contain: size;  /* flex-grow/shrink won't work as expected */
  flex: 1;        /* Ignored—size is fixed */
}

3. Stacking Context Side Effect

/* Both layout and paint create stacking contexts */
.parent {
  contain: layout;  /* Now a stacking context! */
}

.child {
  z-index: 9999;    /* Trapped in parent's context */
}

If you're debugging z-index issues, check all stacking context triggerscontain is often overlooked.

content-visibility: The Auto Pilot

content-visibility builds on containment to automatically skip rendering for off-screen content:

.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 50px;  /* Estimated height */
}

/* Browser will:
   1. Apply contain: layout style paint to off-screen items
   2. Skip layout/paint for items outside viewport
   3. Use contain-intrinsic-size as placeholder height */

For long lists, this can reduce initial render time by 90%+. But beware: content-visibility has accessibility and scrollbar gotchas.

Browser Support

contain has excellent support: Chrome 52+, Firefox 69+, Safari 15.4+, Edge 79+. It's safe to use as progressive enhancement—browsers that don't support it simply ignore it.

/* Safe to use everywhere */
.widget {
  contain: content;
  /* Unsupported browsers: no effect (harmless)
     Supported browsers: performance boost */
}

Practical Recommendations

  • Default choice: contain: content for independent components
  • Virtualized lists: content-visibility: auto with contain-intrinsic-size
  • Fixed-size elements: contain: strict with explicit dimensions
  • Measure first: Use DevTools Performance panel to verify improvement
  • Don't over-apply: Adding to every element creates overhead; target bottlenecks

Key Takeaways

  • contain creates optimization boundaries—browsers skip recalculating outside the element
  • contain: content is the safest all-around choice (layout + paint + style)
  • contain: size requires explicit dimensions or element collapses
  • Paint containment clips overflow like overflow: clip
  • Both layout and paint containment create stacking contexts
  • Use content-visibility: auto for automatic off-screen optimization

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Expert
The 16 Ways CSS Creates Stacking Contexts
8 min
CSS
Deep
Safari Animation Artifacts: The 1px Black Line Glitch
5 min
CSS
Deep
Background Bleed: The Subpixel Rendering Bug
6 min

Advertisement