Mastering the CSS `contain` Property: A Performance Game-Changer
Posted by Nuno Marques on 2 Aug 2025
The CSS contain
property is one of the web's best-kept secrets for performance optimization. While it might not be as flashy as animations or grid layouts, mastering contain
can dramatically improve your website's rendering performance and create more predictable layouts. Let's dive deep into this powerful property and learn when, why, and how to use it effectively.
What is the CSS contain
Property?
The contain
property tells the browser to limit the scope of certain CSS operations to a specific element and its descendants. Think of it as creating a "containment boundary" that prevents layout changes, style recalculations, or paint operations from affecting elements outside that boundary.
.container {
contain: layout style paint;
}
Understanding Containment Types
The contain
property accepts several keywords that control different aspects of containment:
Layout Containment (layout
)
Layout containment ensures that changes inside an element don't affect the layout of elements outside it.
.card {
contain: layout;
/* Changes inside this card won't affect other cards */
}
Use case: Perfect for card grids where individual card content changes shouldn't trigger layout recalculations for the entire grid.
Style Containment (style
)
Style containment limits the scope of CSS counters and ensures that style changes don't leak outside the container.
.article {
contain: style;
counter-reset: section;
}
.article h2::before {
counter-increment: section;
content: counter(section) ". ";
}
Paint Containment (paint
)
Paint containment ensures that descendants can't paint outside their container's bounds, similar to overflow: hidden
but more performance-oriented.
.carousel {
contain: paint;
/* Child elements won't paint outside this container */
}
Size Containment (size
)
Size containment makes an element's size independent of its children's size, requiring explicit dimensions.
.fixed-container {
contain: size;
width: 300px;
height: 200px;
/* Size is now fixed regardless of content */
}
Practical Examples
Example 1: Optimizing a Card Grid
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.card {
contain: layout style paint;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
.card-content {
/* Dynamic content that might change */
}
Here, each card is contained, meaning when content in one card changes (like expanding text or loading images), it won't trigger layout recalculations for other cards.
Example 2: Modal Dialog Optimization
.modal-backdrop {
contain: layout paint;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
contain: style;
background: white;
border-radius: 8px;
padding: 2rem;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
}
Example 3: Virtual Scrolling List
.virtual-list {
contain: strict; /* All containment types */
height: 400px;
overflow: auto;
}
.list-item {
contain: layout paint;
height: 50px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 1rem;
}
Advanced Usage and Shorthand Values
The strict
Keyword
.isolated-component {
contain: strict;
/* Equivalent to: contain: size layout style paint; */
}
The strict
value applies all containment types, creating maximum isolation but requires explicit sizing.
The content
Keyword
.dynamic-content {
contain: content;
/* Equivalent to: contain: layout style paint; */
}
The content
value applies all containment except size, allowing the element to size naturally based on its content.
Browser DevTools and Debugging
Modern browsers provide excellent DevTools support for containment:
.debug-container {
contain: layout paint;
/* Use Chrome DevTools > Rendering > Paint flashing to see containment boundaries */
}
Pro tip: Enable paint flashing in Chrome DevTools to visualize which areas repaint when content changes. Properly contained elements should limit repainting to their boundaries.
When to Use contain
Perfect Use Cases
Component Libraries: Isolate components to prevent style bleeding and improve performance.
.ui-component {
contain: layout style paint;
}
Dynamic Content Areas: Areas where content frequently changes.
.live-updates {
contain: layout paint;
}
Third-party Widgets: Isolate external content that you don't control.
.widget-container {
contain: strict;
width: 300px;
height: 250px;
}
Virtualized Lists: Large lists with dynamic item heights.
.virtual-item {
contain: layout paint;
}
When NOT to Use contain
Avoid These Scenarios
Small, Static Elements: The performance benefit doesn't justify the complexity.
/* Don't do this for simple buttons */
.simple-button {
contain: layout; /* Unnecessary overhead */
}
Elements That Need to Affect Parent Layout: When you need natural sizing or layout participation.
/* Don't use size containment here */
.flexible-content {
/* contain: size; Would break natural sizing */
}
Complex Positioning Scenarios: When elements need to break out of their container bounds.
Performance Benefits and Metrics
The contain
property can provide significant performance improvements:
- Layout Thrashing Reduction: Up to 50-70% improvement in layout calculation time
- Paint Optimization: Reduces paint areas by up to 80% in complex layouts
- Style Recalculation: Prevents cascade recalculations across large DOM trees
Measuring Performance Impact
// Use Performance Observer to measure layout shifts
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Layout shift:', entry.value);
}
});
observer.observe({entryTypes: ['layout-shift']});
Browser Support and Progressive Enhancement
The contain
property has excellent modern browser support:
- Chrome 52+
- Firefox 69+
- Safari 15.4+
For older browsers, use progressive enhancement:
.container {
/* Fallback styles for older browsers */
overflow: hidden;
}
@supports (contain: layout) {
.container {
contain: layout paint;
overflow: visible; /* Reset if containment is supported */
}
}
Common Pitfalls and Solutions
Pitfall 1: Size Containment Breaking Layouts
/* Problem: Content gets clipped */
.broken {
contain: size;
/* No explicit dimensions = 0x0 element */
}
/* Solution: Always provide dimensions with size containment */
.fixed {
contain: size;
width: 300px;
height: 200px;
}
Pitfall 2: Over-containing Elements
/* Problem: Too much containment */
.over-contained {
contain: strict; /* Might be overkill */
}
/* Solution: Use only what you need */
.properly-contained {
contain: layout paint; /* Just what's needed */
}
Pitfall 3: Forgetting About Accessibility
.accessible-container {
contain: layout paint;
/* Ensure focus management still works */
focus-within: /* styles for focus states */
}
Real-World Implementation Tips
Tip 1: Start Small
Begin by adding containment to leaf components that don't affect others:
.icon {
contain: layout paint size;
width: 24px;
height: 24px;
}
Tip 2: Use CSS Custom Properties for Flexibility
.flexible-container {
contain: var(--containment-level, layout paint);
}
/* Override for specific instances */
.special-container {
--containment-level: strict;
width: 400px;
height: 300px;
}
Tip 3: Combine with Container Queries
.responsive-card {
contain: layout paint size;
container-type: inline-size;
}
@container (min-width: 300px) {
.card-content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
Conclusion
The CSS contain
property is a powerful tool for creating performant, predictable layouts. By understanding when and how to use layout, style, paint, and size containment, you can significantly improve your website's performance and create more maintainable CSS architectures.
Start experimenting with contain
in your projects, beginning with isolated components and gradually expanding to more complex use cases. Your users (and your performance metrics) will thank you for the smoother, more responsive experience.
Remember: containment is about finding the right balance between performance optimization and layout flexibility. Use it judiciously, measure its impact, and always prioritize user experience over theoretical performance gains.