How to Isolate Widgets with CSS contain
This guide is part of CSS Containment & content-visibility, within Rendering & CSS Performance. Many pages host small, busy regions: a live score ticker updating every second, a chat panel receiving messages, a countdown timer, a notification badge, a stock price, an ad slot that resizes. Each update changes the DOM, and without containment the browser must consider whether that change affects layout elsewhere — often re-laying out a large part of the document. On complex pages, a tiny text update can cost several milliseconds of layout each time, which adds up to jank and contributes to slow interactions when updates coincide with user input.
CSS containment declares that a widget is independent. contain: content (layout, paint and style containment) keeps layout and paint inside the widget; contain: strict adds size containment when the widget has a fixed size. The browser can then limit work to the widget's subtree.
Rapid Diagnosis
- Record a Performance trace while the widget updates; look at Layout events and their "Layout root" and node counts.
- Enable Paint flashing in the Rendering drawer to see how much repaints on each update.
- Look for update frequency: widgets updating more than once per second are prime candidates.
- Check overflow needs: does the widget show dropdowns, tooltips or shadows outside its box?
Root Cause Analysis
1. Layout invalidation spreads upwards. A size change in a child can change parent sizes and siblings' positions.
2. Paint invalidation can spread. Without paint containment, overflow may require repainting areas outside the widget.
3. Frequent updates multiply the cost. Small costs every second become constant background work.
4. Updates coinciding with input. Interactions that land during a widget update see longer presentation delays.
Step-by-Step Resolution
1. Apply contain: content to independent widgets
.live-ticker, .chat-panel, .stock-quote, .notification-list {
contain: content; /* layout + paint + style */
}
/* trade-off: paint containment clips anything drawn outside the widget box,
including box-shadows of children and absolutely positioned popovers. */
2. Fix the size where possible and use strict
If the widget's size does not depend on its content (a fixed-height ticker bar, a sidebar of fixed width and height), contain: strict adds size containment so content changes never affect outside layout.
.ticker-bar { height: 48px; contain: strict; }
3. Move overflowing UI outside the contained element
Render dropdowns, tooltips and popovers in a top-level layer (the Popover API or a portal), so paint containment does not clip them.
4. Batch widget updates
Combine several data updates into a single DOM update per frame with requestAnimationFrame, so even contained work happens at most once per frame.
Verification
Re-record the trace: Layout events for widget updates should show the widget as the layout root, with node counts matching the widget's size. Paint flashing should highlight only the widget. Visually check for clipped shadows, tooltips and menus. In the field, compare INP on pages with the widget before and after.
Worked Example: A Sports Live Blog
A live sports page updated a score ticker every second and appended entries to a live blog every few seconds. On mid-range phones, each ticker update triggered 8ms of layout across 3,000 nodes, and INP p75 for taps on the page was 310ms because input frequently coincided with updates. The team set the ticker to a fixed height with contain: strict, applied contain: content to each live blog entry, and batched incoming updates per animation frame. Ticker layout dropped to under 0.5ms, live blog appends no longer re-laid out the sidebar, and INP p75 fell to 190ms.
Containment and Third-Party Embeds
Ad slots and third-party widgets often resize unpredictably, pushing content around and triggering layout across the page. Containment cannot stop an iframe from resizing its container if the container's size is content-dependent, but contain: layout on the slot limits the layout effects of changes inside it, and a fixed slot size with contain: strict both prevents layout shifts and isolates layout work. For ad slots, combine a reserved size (to avoid CLS) with containment (to limit layout cost). If the creative must expand, handle it explicitly with a known larger size rather than letting it reflow the page.
Rolling Containment Out Safely
Containment changes rendering semantics, so roll it out widget by widget with visual checks. Start with the widgets that update most often and have the simplest content — tickers, counters, timers. For each, take before and after screenshots in all states (empty, full, error, with tooltips open), and record a short trace to confirm the layout root moved. Then move to richer widgets like chat panels, where popovers and pickers need to move to the top layer first. Keeping each change small makes any clipping or positioning regression easy to trace back to one rule.
Common Mistakes
- Paint containment on widgets with popovers. Clipped menus and tooltips.
- Strict containment on content-sized widgets. The widget collapses to zero size.
- Containing everything. Overusing containment can break legitimate layout relationships, such as percentage heights.
- Ignoring update frequency. Several updates per frame waste work even when contained.
Edge Cases
position: fixed descendants. Layout and paint containment make the element a containing block for fixed-position descendants, which then position relative to the widget rather than the viewport.
z-index and stacking. Paint containment creates a stacking context, which can change how overlapping elements stack.
Counters and quotes. Style containment scopes CSS counters; numbered lists spanning contained regions may restart numbering.
Scroll anchoring. Containment does not prevent scroll anchoring adjustments, but reduces how often they are needed.
FAQ
What is the difference between contain: content and contain: strict?
content is layout, paint and style containment. strict adds size containment, which requires the element to have a size that does not depend on its children.
Does containment help animations?
It can limit layout and paint caused by animated changes within the widget, but compositor-only animations (transform and opacity) avoid that work anyway.
Can containment cause layout bugs?
Yes, if its guarantees are not true for the element: clipping from paint containment and collapsing from size containment are the common ones.
How do I know containment is working?
In the Performance panel, the Layout event's root should be the contained element, and the number of nodes laid out should be roughly the widget's size.
Is containment useful on static pages?
Less so. It matters most where content changes after load. For static long pages, content-visibility: auto is usually the better tool.
Does containment improve INP directly?
It reduces the rendering work triggered by updates and interactions inside the widget, which shortens presentation delay. The effect on INP depends on how much of the interaction's time was layout.
Should design system components include containment?
Only components that are always independent and do not show overflowing content — such as cards with fixed media areas. Most components should leave containment to the page that uses them.
Related
- CSS containment & content-visibility — the topic overview.
- Batching DOM reads and writes — reducing update frequency.
- Debugging CLS caused by dynamic ad injection — the visual-stability side of ad slots.