CSS Containment: contain layout/paint/size Rendering Bails
- Last updated
- Prerequisites:
- CSS layout and paint concepts
- Understanding of style recalc and reflow
- css
- containment
- contain
- layout
- paint
- performance
- rendering
Read at your depth
The practical view
The contain property tells the browser a subtree is independent: contain: layout makes the element independent for layout (floats/positioning inside can't leak out); contain: paint clips painting to the element's box (overflow is clipped and paint can't bleed outside); contain: size makes the element's size independent of its contents (you must size it yourself); contain: content = layout + paint; contain: strict = all three. Use contain: content on widgets whose size is fixed and whose internal changes shouldn't affect the page, to get big rendering wins: the browser skips layout/paint work outside the container.
The same idea in other frameworks
Virtualized React lists wrap items in contain: content so item updates don't re-layout the whole page.CDK virtual scroll / large Angular tables apply containment to rows for scoped invalidation.Vue large-list components (vue-virtual-scroller) pair contain: content with scroll virtualization.Svelte's compiler emits class attributes; adding contain: content classes scopes reflow to each list item.Legacy vs modern
Full-page reflow on widget change vs contained widget
Without containment, updating a widget's internals can invalidate layout far beyond it; contain: content scopes the invalidation to the widget's boundary.
.widget { /* ❌ OLD: no containment — internal changes can re-layout the whole page */ }.widget { contain: content; } /* ✅ NEW: layout+paint scoped to the widget boundary */Interview gotchas
Context
Interviewers want the precise semantics of each containment kind — most people know 'it's for performance' but cannot say what each one does to layout, painting, and sizing.
The mechanical answer
contain: layout establishes the element as an independent formatting context and layout root: internal layout cannot affect elements outside (floats can't escape, and the element's layout is computed independently), so the engine can skip re-layout of ancestors when the subtree changes. contain: paint clips all painting to the element's padding box, and descendants cannot paint outside it; it also creates a stacking context and a containing block for absolutely-positioned descendants. contain: size makes the element's size depend only on its own specified dimensions (width/height or aspect-ratio), ignoring content size entirely — content is laid out within the given box and may overflow. contain: content = layout + paint; contain: strict = layout + paint + size.
Trap
The naive answer is 'contain just makes things faster'. Each keyword changes behavior: size containment can collapse an element whose content grows (if you forget to size it), and paint containment silently clips overflowing content such as dropdowns or tooltips. Another trap: thinking contain: content includes size — it does not, so content that would grow the box still affects the element's own sizing and therefore ancestors. Candidates who can enumerate the three kinds and their side effects show real depth.
Context
This probes the invalidation mechanism — that containment narrows the set of elements that must be re-laid-out or repainted, which is the real performance story.
The mechanical answer
When the DOM changes, the engine computes an invalidation region: the set of elements whose layout/paint may be stale. Without containment, a change deep in a subtree can force re-layout up to the document root (because sizes/collapse can propagate). Containment makes the element an independent root: internal changes only invalidate the contained subtree's own region, and the rest of the page's layout is preserved. Similarly, paint containment limits repaint to the element's box. The win is biggest for many independent siblings (list items, cards, widget tiles) where one change would otherwise dirty the whole container. It is worth adding whenever a subtree is genuinely independent: fixed-size cards, virtualized rows, and widget modules. Adding it to a subtree that depends on outside context can break that dependency.
Trap
The naive answer is 'containment is always free performance'. It changes layout semantics and can break designs that rely on content escaping the box (tooltips, popovers, negative margins). Another trap: using contain: strict on elements that are not explicitly sized — size containment collapses them. Candidates who connect containment to the invalidation region and list real use cases show they understand the trade-off, not just the API.