What if a single line of CSS could slash your page rendering time by up to 80%? That's exactly what the content-visibility property delivers. Now Baseline Newly Available across Chrome, Firefox, Safari, and Edge as of September 2025, this CSS property lets browsers skip layout, paint, and style calculations for off-screen content entirely. The result: dramatically faster initial page loads, better Interaction to Next Paint (INP) scores, and smoother scrolling on content-heavy pages.
I've been testing content-visibility on several production sites over the past few months, and honestly, the rendering improvements still catch me off guard. In this guide, you'll learn how it works under the hood, how to implement it without introducing layout shifts or accessibility regressions, and how to measure the gains in Chrome DevTools. Every code example here is production-ready and tested against the latest browser behavior in 2026.
How content-visibility Works
The content-visibility property controls whether a browser renders an element's contents. When the browser skips rendering, it also skips the expensive layout, paint, and style recalculation work for that element's entire subtree. On a page with hundreds of DOM nodes below the fold, this translates into massive performance savings.
The property accepts three values:
visible— The default. No containment is applied and no rendering is skipped. The element just behaves normally.auto— The browser skips rendering for off-screen content and fully renders it when it approaches the viewport. This is the value you'll use most for performance work.hidden— The element's content is never rendered regardless of viewport position. The rendering state is cached so re-displaying it is fast. Useful for tab panels and SPA view switching.
Understanding the Containment Engine Behind content-visibility
Under the hood, content-visibility: auto applies multiple types of CSS containment simultaneously:
- Layout containment — Changes inside the element can't affect elements outside it, and vice versa. The browser can safely skip the element during layout calculations.
- Style containment — CSS counters and other style-dependent features are scoped to the element and can't leak outward.
- Paint containment — The element's content doesn't paint beyond its boundaries. The browser can skip the entire paint step for off-screen elements.
- Size containment (off-screen only) — When the element is off-screen, the browser treats it as if it has zero intrinsic size unless you provide a placeholder via
contain-intrinsic-size.
This combination of containments is what makes content-visibility so powerful. The browser doesn't merely defer painting — it skips the entire layout tree calculation for the element's subtree.
On pages with complex off-screen content, this can eliminate hundreds of milliseconds of rendering work. That's not a typo — hundreds.
content-visibility: auto in Practice
The auto value is where the real performance gains live. When an element with content-visibility: auto is off-screen, the browser skips rendering its contents entirely. As the element approaches the viewport, the browser renders it just in time for the user to see it.
Basic Implementation
<!-- HTML structure -->
<main>
<article class="post">...</article>
<article class="post">...</article>
<article class="post">...</article>
<!-- dozens more posts -->
</main>
/* Apply to posts below the initial viewport */
.post {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
That's it. Two CSS declarations. The browser will now skip rendering for every .post element that isn't near the viewport, and render them on demand as the user scrolls.
Seriously — two lines.
Selective Application with nth-child
On many pages, the first few items are always visible on load. You can avoid applying containment to those elements to ensure the fastest possible first paint:
/* Skip the first 3 posts — they're above the fold */
.post:nth-child(n + 4) {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
This pattern is especially useful for blog feeds, news sites, and documentation pages where the first few items are always in the initial viewport. I use this approach on pretty much every long-list page I work on now.
Preventing Layout Shifts with contain-intrinsic-size
Here's the catch you need to know about. When content-visibility: auto is applied to an off-screen element, the browser treats it as having zero height by default. As the user scrolls and the element enters the viewport, the browser calculates the real height and the page jumps — a layout shift that hurts your Cumulative Layout Shift (CLS) score.
The fix is contain-intrinsic-size. This property tells the browser what size to use as a placeholder while the element's content isn't rendered:
/* Fixed placeholder height */
.card {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
}
/* Width and height placeholders */
.product-tile {
content-visibility: auto;
contain-intrinsic-size: 300px 450px;
}
The auto Keyword: Smarter Sizing
The auto keyword before the size value is critical for optimal behavior:
.section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
With auto 500px, the browser uses 500px as the initial placeholder. Once the element has been rendered and the browser knows its real height, it remembers that height. If the user scrolls away and the element goes back off-screen, the browser uses the remembered real height instead of reverting to the 500px estimate.
This eliminates scrollbar jitter on subsequent scrolls and is especially valuable for infinite-scroll interfaces where item heights vary. Don't skip the auto keyword — it makes a bigger difference than you'd expect.
content-visibility: hidden for SPA View Switching
The hidden value is a distinct optimization for a different use case: caching rendered views in single-page applications.
Unlike display: none, which destroys the element's rendering state and forces a full re-render when shown again, content-visibility: hidden preserves the cached rendering state. Toggling from hidden to visible is significantly faster because the browser doesn't need to recalculate styles, rebuild layout trees, or repaint from scratch.
/* Tab panel switching */
.tab-panel {
content-visibility: hidden;
}
.tab-panel.active {
content-visibility: visible;
}
Facebook engineers measured up to a 250ms improvement in navigation times when using content-visibility: hidden for caching previously visited views. If your app has heavy tab-based interfaces, complex dashboards, or multi-step wizards, this is a meaningful win worth trying.
Key Differences: hidden vs display: none vs visibility: hidden
| Feature | display: none | visibility: hidden | content-visibility: hidden |
|---|---|---|---|
| Skips rendering | Yes (destroys state) | No | Yes (preserves state) |
| Triggers reflow on toggle | Yes | No | No |
| Occupies layout space | No | Yes | No |
| Accessible to screen readers | No | No | No |
| Fast re-display | Slow (full re-render) | Fast (CSS toggle) | Fastest (cached state) |
Real-World Performance Gains
The numbers from real implementations are striking:
- 7x rendering boost — The web.dev travel blog demo showed initial rendering drop from 232ms to 30ms after applying
content-visibility: autoto chunked content sections. - 80% render time reduction — A real-world test measured initial render time dropping from 340ms to 60ms on a content-heavy page.
- INP improvement from "needs improvement" to "good" — NitroPack demonstrated during a Google webinar that applying
content-visibility: autoreduced the number of elements affected by style recalculation by more than half, dropping INP from 272ms into the green zone. - 250ms faster view switching — Facebook's use of
content-visibility: hiddenfor SPA view caching showed measurable navigation speed improvements.
These gains are most pronounced on pages with large DOM trees — think product listing pages, news feeds, documentation sites, long-form articles with many sections, and dashboards with multiple panels. If your page has a lot of stuff below the fold, you'll likely see a noticeable difference.
How content-visibility Improves INP
Interaction to Next Paint (INP) measures how quickly a page responds to user interactions. As of 2026, 43% of sites still fail the 200ms INP threshold, making it the most commonly failed Core Web Vital.
So how does content-visibility: auto help? Two ways:
- Reduced initial rendering work — By skipping layout and paint for off-screen elements, the main thread has less work during page load. User interactions that happen during or shortly after load compete with less background rendering work.
- Smaller style recalculation scope — When users interact with the page (clicking, typing, tapping), the browser often needs to recalculate styles and perform layout. With
content-visibility: auto, off-screen elements are excluded from these calculations, reducing main thread blocking time.
If your page has a poor INP score and Chrome DevTools shows large "Recalculate Style" events affecting thousands of elements, content-visibility: auto is one of the first optimizations to try. It directly shrinks the number of elements the browser must process during interactions.
Measuring the Impact with Chrome DevTools
You should always verify that content-visibility is delivering actual gains on your specific page. Here's a step-by-step workflow I follow:
Step 1: Record a Baseline Profile
- Open Chrome DevTools and navigate to the Performance tab.
- Enable CPU throttling (use the "Mid-tier mobile" preset for realistic results).
- Click the "Start profiling and reload page" button.
- Let the recording complete automatically.
- Note the total Rendering time in the summary — this includes Style, Layout, and Paint.
Step 2: Apply content-visibility and Re-record
- Add
content-visibility: autoandcontain-intrinsic-sizeto your off-screen sections. - Record a new performance profile with the same throttling settings.
- Compare the Rendering time from both profiles.
Step 3: Check for Regressions
- Inspect the Layout Shift markers in the trace. If you see new layout shifts, adjust your
contain-intrinsic-sizevalues. - Use the Live Metrics panel (available in Chrome DevTools since 2025) to monitor INP, CLS, and LCP in real time as you interact with the page.
- Scroll through the entire page and confirm content renders smoothly without visible pop-in or jank.
// Programmatic check: verify content-visibility is applied
document.querySelectorAll('.post').forEach(el => {
const style = getComputedStyle(el);
console.log(el.id, 'content-visibility:', style.contentVisibility);
});
Browser Support and Known Caveats
As of September 2025, content-visibility is Baseline Newly Available, meaning it's supported in the latest versions of all major browsers:
- Chrome/Edge — Supported since Chrome 85 (August 2020). The most mature implementation.
- Firefox — Full support added in Firefox 125 (2024).
- Safari — Support landed in Safari 18 (September 2025), completing the Baseline status.
Known Issues to Watch For
- Safari find-in-page limitation — Safari's native Cmd+F search may not find text inside elements with
content-visibility: autothat are currently off-screen. If searchability is critical (documentation sites, knowledge bases), test this behavior thoroughly and consider excluding key sections fromcontent-visibility. - Scrollbar jitter — When
contain-intrinsic-sizeestimates are significantly different from actual content heights, rapid scrollbar dragging can cause visible jitter. Using theautokeyword mitigates this on subsequent scrolls. - CSS Grid and multi-column layouts — Elements inside grid or multi-column containers may behave differently with containment applied. The explicit placeholder size is treated differently than implicit content-based height. Test these layouts carefully.
- Do not apply to above-the-fold content — Adding
content-visibility: autoto elements visible in the initial viewport delays their rendering, effectively acting like lazy loading for your LCP element. This will hurt LCP scores.
Best Practices Checklist
Here's the checklist I run through when implementing content-visibility on a site:
- Never apply to above-the-fold content. Only target sections that are below the initial viewport on both desktop and mobile.
- Always pair with
contain-intrinsic-size. Use theautokeyword plus a reasonable pixel estimate to prevent CLS and scrollbar instability. - Apply to large, self-contained sections. Blog posts in a feed, product cards in a grid, comment threads, documentation sections — these are ideal targets. Don't bother applying it to tiny elements where the rendering cost is already negligible.
- Test on real devices. The performance benefits are most visible on mid-tier mobile devices where CPU is constrained. Use Chrome DevTools' mobile throttling presets.
- Use nth-child to exclude early items. If the first few items are always visible, skip them with a selector like
:nth-child(n + 4). - Validate with before/after profiling. Record performance traces with and without the property. Compare Rendering time, Style/Layout/Paint breakdown, and CLS scores.
- Monitor INP in the field. Use the Web Vitals library or a RUM tool to track whether INP improves after deployment.
- Consider
content-visibility: hiddenfor tab panels. If your app has tabbed interfaces or multi-view layouts, this value gives you instant view switching without destroying cached render state.
Complete Implementation Example
Let's put it all together. Here's a full example showing content-visibility applied to a product listing page:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
padding: 1.5rem;
}
/* First row is always visible — do not defer */
.product-card:nth-child(n + 5) {
content-visibility: auto;
contain-intrinsic-size: auto 420px;
}
</style>
</head>
<body>
<h1>Products</h1>
<div class="product-grid">
<div class="product-card">
<img src="product-1.avif" alt="Product 1"
width="280" height="280" loading="lazy">
<h2>Product Name</h2>
<p>$29.99</p>
</div>
<!-- Repeat for additional products -->
</div>
</body>
</html>
And here's a JavaScript-driven tab panel using content-visibility: hidden:
<div class="tabs">
<button data-tab="overview" class="active">Overview</button>
<button data-tab="specs">Specs</button>
<button data-tab="reviews">Reviews</button>
</div>
<div id="overview" class="tab-panel active">...</div>
<div id="specs" class="tab-panel">...</div>
<div id="reviews" class="tab-panel">...</div>
<style>
.tab-panel {
content-visibility: hidden;
}
.tab-panel.active {
content-visibility: visible;
}
</style>
<script>
document.querySelector('.tabs').addEventListener('click', (e) => {
if (!e.target.dataset.tab) return;
document.querySelectorAll('.tab-panel').forEach(p =>
p.classList.remove('active')
);
document.querySelectorAll('.tabs button').forEach(b =>
b.classList.remove('active')
);
document.getElementById(e.target.dataset.tab).classList.add('active');
e.target.classList.add('active');
});
</script>
Frequently Asked Questions
Does content-visibility: auto affect SEO or Googlebot rendering?
No. Content inside elements with content-visibility: auto remains in the DOM and is fully accessible to search engine crawlers. Googlebot doesn't need to scroll to discover the content — it parses the full DOM regardless of viewport visibility. Your text, links, and structured data are all indexable as normal.
Can I use content-visibility with CSS Grid or Flexbox layouts?
Yes, but test carefully. CSS containment changes how the browser handles sizing within grid and multi-column layouts. An explicitly set contain-intrinsic-size is treated differently from implicit content-based height in these contexts. In standard single-column layouts and basic grid setups, it works without issues. For complex multi-column or masonry-style grids, profile before and after to confirm there are no layout regressions.
What's the difference between content-visibility and native lazy loading?
Native lazy loading (loading="lazy") defers the network fetch of images and iframes. content-visibility: auto defers the rendering (layout, style, paint) of entire DOM subtrees. They solve different problems and actually work well together — use loading="lazy" on off-screen images to save bandwidth, and content-visibility: auto on content sections to save rendering time.
Will content-visibility: auto break accessibility?
No. Off-screen content with content-visibility: auto remains in the accessibility tree. Screen readers can still access it, and keyboard navigation (tabbing) works normally. The spec explicitly requires that content remain accessible even when rendering is skipped. However, content-visibility: hidden does remove content from the accessibility tree, similar to display: none.
How do I choose the right contain-intrinsic-size value?
Start with an estimate of the average rendered height of your elements. It doesn't need to be pixel-perfect — an approximate value within 100-200px of the real height is sufficient. Always use the auto keyword prefix (e.g., auto 500px) so the browser remembers the actual rendered height after the first paint and uses it for subsequent scroll-away events. Monitor CLS scores after deployment to confirm your estimates are close enough.