The contain Property for Animation Performance
The cheapest animations only touch transform and opacity. Real interfaces cannot always stay inside that pair: a widget whose content changes size, a live-updating counter, an accordion that genuinely pushes content down, a loading placeholder whose text swaps in. Each triggers layout or paint, and by default the browser has to assume the change might affect anything on the page. The contain property removes that assumption. It is a promise from the author — "nothing in here affects anything out there" — that the browser uses to restrict layout and paint to one subtree. This page explains the containment types, where each helps animation, and the side effects that make containment a deliberate choice rather than a blanket optimisation. It belongs to Performance & GPU Acceleration in the CSS-Only Micro-Interactions & Animations guide.
The containment types
contain accepts four individual types and two shorthands:
layout— the element's internal layout is independent of the rest of the page. Changes inside it do not cause layout outside it. The element becomes a containing block for absolutely and fixed-positioned descendants and establishes an independent formatting context.paint— descendants are not painted outside the element's bounds; overflow is clipped. The browser can skip painting the subtree entirely when the element is off screen.size— the element's size is computed as if it had no children. It must get its size from elsewhere — explicit dimensions orcontain-intrinsic-size.style— counters and quotes inside the element do not affect the outside. Rarely relevant to animation.content— shorthand forlayout paint style. The practical default for components.strict— shorthand forsize layout paint style.
The complete implementation
A live stock ticker widget whose values update and briefly flash, inside a larger dashboard. The widget's content changes size as numbers change, which would otherwise make the browser re-examine the surrounding layout on every update.
.ticker {
/* Layout and paint work inside stays inside. */
contain: content;
position: relative; /* anchor for the badge without containment too */
/* Reserve a stable box so the contained widget never resizes the page. */
inline-size: 100%;
min-block-size: 6rem;
padding: 1rem;
border-radius: 10px;
background: #0f172a;
color: #e2e8f0;
}
.ticker__value {
font-variant-numeric: tabular-nums; /* digits keep the same width */
transition: color 600ms ease-out;
}
.ticker__value.is-up { color: #4ade80; transition-duration: 0s; }
.ticker__value.is-down { color: #f87171; transition-duration: 0s; }
/* A badge that overflows the corner: clipped by paint containment. */
.ticker__badge {
position: absolute;
inset-block-start: -0.5rem;
inset-inline-end: -0.5rem;
}
The demo shows the side effect directly: two widgets with the same overflowing corner badge. The widget without containment shows the badge hanging over its edge; the one with contain: content clips it. That clipping is not a bug — it is exactly what makes paint containment fast, because the browser knows nothing inside can draw outside the box.
The value flash uses asymmetric transitions. Adding is-up or is-down applies the colour instantly, because those rules set transition-duration: 0s; removing the class a moment later lets the colour fade back over 600 milliseconds, because the base rule's duration applies to the return. Colour changes trigger paint, and on a dashboard with dozens of tickers updating every second, that paint is exactly the work containment keeps local.
Variation: containment without clipping
If a component must let something overflow — the badge above, a focus ring drawn outside the border, a shadow — drop paint containment and keep the rest: contain: layout style. Layout work is still confined to the subtree, which is usually the larger saving for components whose content changes size, while painting is no longer clipped. The component still becomes a containing block and a stacking context, so check absolutely positioned descendants and z-index values. For components that change content but never size, such as a ticker with fixed-width digits inside a fixed-height box, contain: layout alone often captures most of the benefit.
The key technique: contain the region that changes, not the page
Containment pays off when three things are true: the contained element's contents change often (animation, live data, lazy-loaded text), the changes would otherwise trigger layout or paint, and the element sits in a large or complex page where page-wide work is expensive. It does little for elements that only animate transform or opacity, which already skip layout and paint.
Side effects to plan for
Containment changes behaviour, not just performance. Before adding it to a component, check each of these:
- Clipping. Paint containment clips at the padding box. Anything designed to overflow — a dropdown menu, a tooltip, a badge, an outer focus ring, a large shadow — is cut off. Move such elements outside the contained box, render them in the top layer with the
popoverattribute or<dialog>, or usecontain: layout stylewithoutpaint. - Positioning context. Layout and paint containment make the element the containing block for
position: fixeddescendants. A "fixed" modal inside a contained card is positioned relative to the card, not the viewport. Top-layer elements are exempt. - Stacking context. Layout and paint containment each create a stacking context. Descendants'
z-indexvalues are resolved inside it, so a child can no longer rise above an element outside the component. - Size containment. With
size(included instrict), an element without explicit dimensions collapses to zero, because its children no longer contribute to its size. Pair it with fixed sizing orcontain-intrinsic-size.
Because of these, contain: content belongs on self-contained components with predictable bounds — cards, widgets, list items — and not on layout wrappers that host overlays. A useful rule of thumb: if you would be comfortable giving the element overflow: clip and treating it as its own positioning root, it is a good candidate for contain: content.
Containment and content-visibility
content-visibility: auto builds on containment: it applies layout, style and paint containment and skips rendering the element's contents entirely while it is off screen. For long pages with many animated or heavy sections, it keeps off-screen sections from costing anything until the user scrolls near them. It also implies size containment while skipped, which is why it needs contain-intrinsic-size to avoid scrollbar jumps. content-visibility and contain-intrinsic-size covers that pairing, including the accessibility considerations for skipped content.
Measuring the difference
Containment should be justified by measurement, not applied everywhere by habit. Record the interaction in Chromium's Performance panel with and without contain. In the "Layout" events, the summary lists the number of nodes that needed layout and the layout root. With containment, the layout root should be the contained element and the node count should drop to its subtree. If the numbers do not change, the browser was already able to scope the work, and containment adds side effects for no benefit. Measure on a mid-range phone as well as a development machine: containment often makes no visible difference on a fast laptop and a large one on a device with a slower processor, where page-wide layout for each update is the difference between a smooth dashboard and one that visibly stutters as data arrives.
Browser support
The contain property is supported in Chrome 52+, Edge 79+, Firefox 69+ and Safari 15.4+. content-visibility is supported in Chrome and Edge 85+, Firefox 125+ and Safari 18+, and contain-intrinsic-size in Chrome and Edge 83+, Firefox 107+ and Safari 17+. Browsers that ignore contain render identically minus the optimisation, apart from the clipping and positioning differences, so test overflowing elements in a supporting browser.
FAQ
What does contain: layout do? It tells the browser that nothing inside the element affects layout outside it, and vice versa. When the element's contents change, the browser can lay out just that subtree instead of checking the rest of the page.
Why did contain: paint cut off my dropdown?
Paint containment clips everything to the element's padding box, like overflow: clip. Popovers, tooltips, focus rings and shadows that extend beyond the box are cut off. Keep overflowing elements outside the contained element or in the top layer.
What is the difference between contain: content and contain: strict?content is shorthand for layout, paint and style containment. strict adds size containment, which means the element's size ignores its children; without an explicit size it collapses to zero. content is the safe default for components.
Does contain help transform and opacity animations?
Not much, because those already skip layout and paint. contain is most useful when an animation, or the content inside an animated region, changes layout or paint and you want that work confined to the component.
Related
- Performance & GPU Acceleration — the parent guide.
- Layout, Paint and Composite: What Triggers What — which changes need containing.
- Profiling Animations in DevTools — measuring layout scope.
- Popover Entry and Exit Transitions — overlays that escape containment via the top layer.
Related articles
More pages in the same section.