The content-visibility property controls whether an element renders its contents, enabling significant performance improvements. Here's how to use it.
Basic Usage#
1/* Skip rendering off-screen content */
2.section {
3 content-visibility: auto;
4 contain-intrinsic-size: auto 500px;
5}
6
7/* Possible values */
8.visible {
9 content-visibility: visible; /* Default - always render */
10}
11
12.hidden {
13 content-visibility: hidden; /* Never render (like display:none but preserves state) */
14}
15
16.auto {
17 content-visibility: auto; /* Browser decides based on visibility */
18}Long Page Optimization#
1/* Optimize long scrolling pages */
2article {
3 content-visibility: auto;
4 contain-intrinsic-size: 0 800px; /* Estimated height */
5}
6
7/* Blog post sections */
8.post-section {
9 content-visibility: auto;
10 contain-intrinsic-size: auto 400px;
11}
12
13/* Product cards in grid */
14.product-card {
15 content-visibility: auto;
16 contain-intrinsic-size: 300px 400px; /* width height */
17}
18
19/* Comment threads */
20.comment {
21 content-visibility: auto;
22 contain-intrinsic-size: 0 150px;
23}contain-intrinsic-size#
1/* Fixed intrinsic size */
2.item {
3 content-visibility: auto;
4 contain-intrinsic-size: 200px 300px; /* width height */
5}
6
7/* Height only (width from layout) */
8.item {
9 content-visibility: auto;
10 contain-intrinsic-size: 0 300px;
11}
12
13/* Auto with fallback */
14.item {
15 content-visibility: auto;
16 contain-intrinsic-size: auto 300px;
17 /* Uses last known size, falls back to 300px */
18}
19
20/* Individual properties */
21.item {
22 content-visibility: auto;
23 contain-intrinsic-width: 200px;
24 contain-intrinsic-height: 300px;
25}
26
27/* Block and inline */
28.item {
29 content-visibility: auto;
30 contain-intrinsic-block-size: 300px;
31 contain-intrinsic-inline-size: 200px;
32}Hidden vs display:none#
1/* display:none - removes from layout, resets state */
2.hidden-display {
3 display: none;
4}
5
6/* content-visibility:hidden - keeps in layout, preserves state */
7.hidden-content {
8 content-visibility: hidden;
9}
10
11/* Use case: Tab panels */
12.tab-panel {
13 content-visibility: hidden;
14}
15
16.tab-panel.active {
17 content-visibility: visible;
18}
19
20/* Form state is preserved when hidden */
21.form-section {
22 content-visibility: hidden;
23}
24
25.form-section.active {
26 content-visibility: visible;
27}
28/* Input values, scroll position, etc. are maintained */Lazy Rendering Pattern#
1/* Lazy sections that render when visible */
2.lazy-section {
3 content-visibility: auto;
4 contain-intrinsic-size: auto 600px;
5}
6
7/* Cards in infinite scroll */
8.card-container {
9 display: grid;
10 grid-template-columns: repeat(3, 1fr);
11 gap: 1rem;
12}
13
14.card {
15 content-visibility: auto;
16 contain-intrinsic-size: auto 350px;
17}
18
19/* Image gallery */
20.gallery-item {
21 content-visibility: auto;
22 contain-intrinsic-size: 300px 300px;
23}
24
25/* Timeline items */
26.timeline-item {
27 content-visibility: auto;
28 contain-intrinsic-size: 0 200px;
29 padding: 1rem;
30 margin-bottom: 1rem;
31}Dashboard Widgets#
1/* Dashboard with many widgets */
2.dashboard {
3 display: grid;
4 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
5 gap: 1rem;
6}
7
8.widget {
9 content-visibility: auto;
10 contain-intrinsic-size: auto 250px;
11 padding: 1rem;
12 background: white;
13 border-radius: 8px;
14}
15
16/* Chart widgets */
17.chart-widget {
18 content-visibility: auto;
19 contain-intrinsic-size: auto 400px;
20}
21
22/* Table widgets */
23.table-widget {
24 content-visibility: auto;
25 contain-intrinsic-size: auto 500px;
26}Combining with Containment#
1/* Full containment for maximum optimization */
2.optimized-section {
3 content-visibility: auto;
4 contain: layout style paint;
5 contain-intrinsic-size: auto 400px;
6}
7
8/* Strict containment */
9.strict-section {
10 content-visibility: auto;
11 contain: strict;
12 contain-intrinsic-size: 100% 300px;
13}
14
15/* Size containment only */
16.size-contained {
17 content-visibility: auto;
18 contain: size;
19 contain-intrinsic-size: 300px 200px;
20}Accordion Pattern#
1/* Accordion with content-visibility */
2.accordion-panel {
3 content-visibility: hidden;
4 contain-intrinsic-size: 0 0;
5 height: 0;
6 overflow: hidden;
7 transition: height 0.3s ease;
8}
9
10.accordion-panel.open {
11 content-visibility: visible;
12 contain-intrinsic-size: auto;
13 height: auto;
14}
15
16/* Alternative: keep size but hide */
17.accordion-content {
18 content-visibility: hidden;
19}
20
21.accordion-item.active .accordion-content {
22 content-visibility: visible;
23}Modal and Dialog#
1/* Modal optimization */
2.modal-backdrop {
3 content-visibility: hidden;
4 position: fixed;
5 inset: 0;
6 background: rgba(0, 0, 0, 0.5);
7}
8
9.modal-backdrop.open {
10 content-visibility: visible;
11}
12
13.modal-content {
14 /* Render-heavy content */
15 content-visibility: inherit;
16}
17
18/* Off-canvas menu */
19.off-canvas {
20 content-visibility: hidden;
21 position: fixed;
22 left: 0;
23 top: 0;
24 width: 300px;
25 height: 100vh;
26 transform: translateX(-100%);
27 transition: transform 0.3s;
28}
29
30.off-canvas.open {
31 content-visibility: visible;
32 transform: translateX(0);
33}Table Optimization#
1/* Large table optimization */
2.data-table {
3 width: 100%;
4 border-collapse: collapse;
5}
6
7.data-table tbody tr {
8 content-visibility: auto;
9 contain-intrinsic-size: auto 50px;
10}
11
12/* Grouped rows */
13.table-group {
14 content-visibility: auto;
15 contain-intrinsic-size: auto 200px;
16}
17
18/* Virtual scrolling enhancement */
19.virtual-row {
20 content-visibility: auto;
21 contain-intrinsic-size: 100% 40px;
22}Animation Considerations#
1/* Avoid content-visibility on animated elements */
2.animated-card {
3 /* Don't use content-visibility: auto here */
4 /* It can interfere with animations */
5}
6
7/* Use for static sections around animations */
8.static-section {
9 content-visibility: auto;
10 contain-intrinsic-size: auto 300px;
11}
12
13/* Animated section remains visible */
14.animated-section {
15 content-visibility: visible;
16}JavaScript Integration#
1// Detect when content becomes visible
2const section = document.querySelector('.lazy-section');
3
4// contentvisibilityautostatechange event
5section.addEventListener('contentvisibilityautostatechange', (e) => {
6 if (e.skipped) {
7 console.log('Section is hidden');
8 } else {
9 console.log('Section is visible');
10 // Load data, start animations, etc.
11 }
12});
13
14// Check current state
15if (section.matches(':visible')) {
16 // Content is being rendered
17}
18
19// Force visibility
20function ensureVisible(element) {
21 element.style.contentVisibility = 'visible';
22}
23
24// Intersection Observer fallback
25const observer = new IntersectionObserver((entries) => {
26 entries.forEach((entry) => {
27 if (entry.isIntersecting) {
28 entry.target.style.contentVisibility = 'visible';
29 }
30 });
31});Performance Measurement#
1// Measure rendering performance
2const start = performance.now();
3
4// Apply content-visibility
5document.querySelectorAll('.section').forEach((section) => {
6 section.style.contentVisibility = 'auto';
7 section.style.containIntrinsicSize = 'auto 400px';
8});
9
10// Force style recalculation
11document.body.offsetHeight;
12
13const end = performance.now();
14console.log(`Optimization applied in ${end - start}ms`);
15
16// Monitor layout shifts
17const observer = new PerformanceObserver((list) => {
18 for (const entry of list.getEntries()) {
19 console.log('Layout shift:', entry.value);
20 }
21});
22
23observer.observe({ type: 'layout-shift', buffered: true });Best Practices#
Usage:
✓ Use for off-screen content
✓ Set contain-intrinsic-size
✓ Use 'auto' for most cases
✓ Apply to repeated elements
Performance:
✓ Reduces initial render time
✓ Skips off-screen layout
✓ Improves scroll performance
✓ Preserves element state
Intrinsic Size:
✓ Estimate accurately
✓ Use 'auto' prefix when possible
✓ Match typical content size
✓ Update if content varies
Avoid:
✗ On visible above-fold content
✗ On animated elements
✗ Without intrinsic size
✗ Causing layout shifts
Conclusion#
The content-visibility property significantly improves rendering performance by skipping off-screen content. Use it for long pages, lists, and hidden UI elements. Always set contain-intrinsic-size to prevent layout shifts. The 'auto' value lets the browser optimize automatically based on visibility.