content-visibility & intrinsic-size
Rendering a page isn't free even for content the user hasn't scrolled to yet — the browser still calculates layout, style, and paint for everything in the DOM. content-visibility: auto changes that: it tells the browser to skip rendering work entirely for off-screen content, and do it lazily as the user scrolls near it. For long pages, this is one of the largest, lowest-effort performance wins available in CSS.
The rendering cost model
On a normal page, adding 1,000 off-screen list elements to a feed still costs the browser style + layout + paint time for every one of them, even though the user only sees 10. content-visibility: auto defers that cost until the element is actually about to become visible.
.feed-item {
content-visibility: auto;
}Without content-visibility -> browser renders all 1,000 items up front With content-visibility: auto -> browser renders ~10 visible items, defers the rest
contain-intrinsic-size — the placeholder size
When an element's rendering is skipped, the browser doesn't know its real size (since it never laid out the content). Without a hint, it collapses to zero height — which breaks scrollbar length and causes jarring layout shifts as items pop in. contain-intrinsic-size gives the browser a placeholder size to reserve instead.
.feed-item {
content-visibility: auto;
contain-intrinsic-size: auto 400px; /* reserve ~400px until rendered */
}auto keyword tells the browser to remember the element's *last actual rendered size* the next time it becomes visible, and reuse that as the placeholder size the next time it goes off-screen — much more accurate than a single guessed number for content with variable heights.Measuring the improvement
Open DevTools Performance panel, record a full page load or a fast scroll through a long list
Compare total "Rendering" time (style + layout + paint) with and without
content-visibility: autoFor long feeds/articles, expect layout and paint time on initial render to drop dramatically since most content is deferred
Also check Cumulative Layout Shift (CLS) in Lighthouse — a well-tuned
contain-intrinsic-sizeshould keep it near zero
/* A realistic long-article setup */
article > section {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
/* The very first section should render immediately (above the fold) */
article > section:first-child {
content-visibility: visible;
}Pitfall 1: find-in-page (Ctrl/Cmd+F)
content-visibility: auto is not removed from the DOM, and modern Chromium/Firefox do account for it during in-page search — but this support has evolved over browser versions, and older engines may fail to find text inside not-yet-rendered sections. Test find-in-page behavior on your actual target browsers before relying on it for critical content.Pitfall 2: accessibility tree timing
content-visibility: auto directly to it.Pitfall 3: nested content-visibility
Applying content-visibility: auto to both a parent and its children is usually redundant — once the parent's rendering is skipped, its children's rendering is skipped along with it. Apply it at one sensible level (usually a repeating list item or a page section), not at every nesting level.
When to use it
Good fit | Poor fit |
|---|---|
Long social feeds, comment threads, chat histories | Short pages where everything is above the fold anyway |
Long-form articles broken into sections | Content that must be instantly searchable/accessible regardless of scroll position |
Large data tables or grids rendered in full | Small, already-fast components — the tuning overhead is not worth it |
content-visibility vs JavaScript virtualization
Before content-visibility existed, the only way to get this benefit was JavaScript-based list virtualization (react-window, react-virtualized) — libraries that manually remove off-screen DOM nodes and re-create them on scroll. Both approaches solve the same problem; the tradeoffs differ.
Approach | Pros | Cons |
|---|---|---|
| Pure CSS, no library, keeps real DOM nodes (find-in-page mostly works, simpler mental model) | Less precise control over exactly what renders; needs |
JS virtualization library | Precise control, works even for extremely large lists (tens of thousands of rows) | Extra dependency, more complex, own accessibility and scroll-restoration edge cases |
content-visibility: auto to the items that a virtualization library *does* keep mounted for buffering purposes, layering the CSS-level win on top of the JS-level one for very large lists.Interaction with loading="lazy" images
content-visibility: auto and native image lazy-loading solve related but different problems — one defers rendering work for a whole subtree, the other defers network requests for a single image. They compose cleanly.
<section class="feed-item"> <img src="/photo.jpg" loading="lazy" alt="Description" /> <p>Caption text</p> </section>
.feed-item {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
}
/* The image inside still gets its own network-level laziness from
loading="lazy"; content-visibility additionally defers the
rendering (layout/style/paint) cost of the whole section */A rollout checklist
Applying content-visibility: auto across an existing page is safest as a staged process rather than a blanket find-and-replace — the tuning of contain-intrinsic-size is what determines whether it helps or hurts.
Identify the single longest, most repeated content pattern on the page (feed items, table rows, article sections)
Apply
content-visibility: autoto that one pattern first, without touching anything elseSet an initial
contain-intrinsic-sizeguess based on the typical rendered height of one itemMeasure Cumulative Layout Shift in Lighthouse before and after — a bad guess shows up immediately as new layout shift
Switch to
contain-intrinsic-size: auto <height>so the browser remembers real sizes after first render, rather than a single fixed guessRepeat for the next-longest content pattern once the first is confirmed to help
Browser support caveats
content-visibility is supported in Chromium-based browsers and Firefox but historically lagged in Safari. Because it is purely a rendering optimization and not a visual requirement, unsupported browsers simply render everything eagerly (the old, slower behavior) rather than breaking — always verify this is actually true for the specific property/value combination you rely on before shipping it as a hard performance requirement.Restoring visibility for print or export
Skipped rendering is a screen-only performance optimization — it should not be relied upon to control what appears when a page is printed or exported to PDF, since those contexts often force full rendering anyway. Still, it is worth being explicit for print stylesheets.
@media print {
.feed-item {
content-visibility: visible; /* force full rendering when printing */
}
}Summary
content-visibility: autodefers style/layout/paint work for off-screen content, a major win on long pagesAlways pair it with
contain-intrinsic-sizeto avoid layout shift from collapsing, zero-height placeholdersPrefer the
auto <height>form so the browser remembers real sizes after the first renderRoll it out incrementally, measuring CLS at each step, rather than applying it everywhere at once
Be mindful of find-in-page and accessibility-tree timing for content that must always be discoverable