content-visibility: Rendering Long Pages Fast
AI generated
{ }
@
CSS · Rendering Performance · Web Performance
content-visibility: Rendering Long Pages Fast
Saving rendering work without losing content

Long product listings, article feeds and documentation pages force the browser to calculate layout and style for thousands of elements, even though only a fraction of them is ever visible. content-visibility skips this work for offscreen regions on purpose, turning a sluggish long page into one that feels as fast as a short one.

14 min read content-visibility · contain-intrinsic-size · rendering Chrome · Edge · Firefox 125+ · Safari 18+

1. Why long pages slow rendering down

A browser calculates style, layout and, in many cases, paint for every element in the document, regardless of whether that element is currently within the visible viewport. On a page with fifty elements that overhead is invisible. On a product listing with two thousand cards, a comment section with hundreds of entries, or a long technical documentation page, that work adds up to noticeable delays during initial render and on every reflow. This is exactly where content-visibility comes in: the property allows the browser to skip rendering work for content outside the viewport entirely, until it is actually needed.

The key difference from classic lazy loading is that content-visibility does not delay data loading, it relieves the rendering pipeline itself. The DOM node still exists, its content is present in the accessibility tree, but layout and paint are only computed once the region enters the viewport. For pages with hundreds or thousands of elements, this is often the single most effective lever for improving time to interactive and scroll performance without changing the structure of the page at all.

2. The values of content-visibility in detail

The content-visibility property has three relevant values: visible, hidden and auto. visible is the default and changes nothing, the element renders normally. hidden removes the content from rendering entirely, similar to display: none, but unlike that keeps the element's internal state intact, such as scroll positions in nested containers or form input values. The value that matters most for performance optimization is auto: the browser decides on its own whether an element is rendered, based on whether it is inside or near the visible viewport.

With content-visibility: auto the browser skips layout, paint and in many cases style recalculation for elements outside the viewport, while keeping an approximate size for the element so the scrollbar height and the position of other elements stay stable. As soon as the element scrolls near the viewport, the browser renders it normally, usually before it becomes visible, so no popping-in effect occurs. This combination of skipped work and automatic reactivation makes content-visibility: auto the central property for long lists, feeds and documentation pages.


/* Basic content-visibility setup for a long feed of cards */
.feed-item {
  content-visibility: auto;
  /* Reserve space so scrollbar height stays stable
     before the browser has ever rendered this item */
  contain-intrinsic-size: auto 320px;
}

/* content-visibility: hidden keeps internal state,
   unlike display: none which resets it completely */
.tab-panel[data-active="false"] {
  content-visibility: hidden;
}

.tab-panel[data-active="true"] {
  content-visibility: visible;
}

3. contain-intrinsic-size: preventing layout jumps

Without a size hint, an element with content-visibility: auto collapses to zero height while unrendered, because the browser performs no layout calculation and therefore has no idea of its natural size. The result would be a jumping scrollbar and an unstable layout as elements render in and out. The contain-intrinsic-size property solves this by defining a placeholder size the browser uses as long as the actual content is not rendered.

The value contain-intrinsic-size: auto 320px combines two behaviors: the auto keyword tells the browser to remember the last actually rendered size and reuse that size instead of the fixed value once the element has been hidden again, the fixed pixel value only serves as an initial fallback before the first render. For cards with widely varying heights, such as comments with variable text length, this combination is far more robust than a single fixed value, because it adapts to each element's real height after the first pass and keeps layout jumps to a minimum.

4. Practical example: a feed with hundreds of cards

In a typical use case, a news feed or a product overview with several hundred cards, content-visibility: auto is applied directly to the individual card containers, not to the whole list. Each card additionally gets an estimated height through contain-intrinsic-size, ideally derived from the average of already rendered cards of the same type. For a feed with mixed card types, such as text posts and image posts, a different height estimate per type is recommended so the initial layout shift on first render stays as small as possible.

A real example: a documentation page with sixty sections, each with several paragraphs and sometimes code samples, benefits enormously from content-visibility: auto at the section level. Before the optimization, the initial rendering of all sixty sections takes noticeably long, especially on weaker mobile devices. After the optimization, the browser only renders the initially visible sections, the rest is computed as the user scrolls down. The measured improvement in rendering time for such pages is often in the range of fifty to ninety percent, depending on the total number and complexity of the sections.


/* Documentation page: apply content-visibility per section,
   not to the wrapping container */
.doc-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

/* Mixed feed: different estimated heights per card type
   reduces initial layout shift when the card first renders */
.card--text {
  content-visibility: auto;
  contain-intrinsic-size: auto 180px;
}

.card--image {
  content-visibility: auto;
  contain-intrinsic-size: auto 420px;
}

5. content-visibility vs. display: none vs. visibility: hidden

All three properties sound similar but differ fundamentally in effect. display: none removes an element from layout and the accessibility tree entirely, any internal state is lost, a video pauses, a form field is reset when shown again unless it was saved externally. visibility: hidden keeps the space in the layout but makes the element invisible and non interactive, the rendering cost stays fully intact, so no work is actually saved.

content-visibility: hidden is a middle ground: the content is not rendered and therefore consumes no layout or paint resources, but the internal state is preserved because the DOM node is not removed. For tab panels, accordions and modals where repeated showing and hiding without losing state matters, content-visibility: hidden is often the better choice over display: none, especially when the hidden panels themselves contain complex content with their own state.

A frequently overlooked aspect of content-visibility: auto concerns the browser's built in Ctrl+F search. Since unrendered content is invisible to the browser, a naive search would miss matches inside hidden regions. Modern browsers solve this through the beforematch event: as soon as the native search finds a match inside a region with content-visibility: auto, this event fires and the region is automatically rendered and scrolled into view, before the user even notices anything.

The same logic applies to keyboard navigation and anchor links: if a user jumps directly to #section-42 inside a region with content-visibility: auto, the browser automatically activates rendering for that region so the anchor target is actually reachable. This behavior is part of the specification and works without any extra JavaScript, which makes content-visibility considerably more robust than handrolled lazy rendering solutions that often do not cover these edge cases.

7. Measuring with the DevTools rendering panel and performance trace

To prove the effect of content-visibility, a subjective impression is not enough. In the Chrome DevTools performance panel, a trace recorded before the optimization typically shows long, continuous bars for Recalculate Style and Layout right after the page loads. After adding content-visibility: auto to the relevant containers, these bars shrink noticeably, because only the visible elements are computed. Additionally, the rendering tab option "Layout Shift Regions" can be used to check whether contain-intrinsic-size is calibrated correctly, visible jumps indicate an inaccurate height estimate.

For automated measurements, the browser's performance API works well, in particular PerformanceObserver with the long-animation-frame entry type for detecting long rendering blocks, along with Lighthouse for aggregated metrics like Largest Contentful Paint and Total Blocking Time. On pages using content-visibility, pay particular attention to First Contentful Paint, which usually arrives noticeably earlier thanks to the reduced initial rendering, while Largest Contentful Paint stays stable as long as the visible hero element itself is not affected by the optimization.


// Measure rendering cost reduction from content-visibility
// using the Long Animation Frames API
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`Long frame: ${entry.duration.toFixed(1)}ms`, entry);
  }
});
observer.observe({ type: "long-animation-frame", buffered: true });

// Detect layout shifts caused by inaccurate contain-intrinsic-size
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log(`Layout shift value: ${entry.value}`);
    }
  }
}).observe({ type: "layout-shift", buffered: true });

8. Combining with containment and lazy loading

content-visibility: auto automatically implies contain: layout style paint for elements whose content is currently unrendered. This means the effects described in this article overlap closely with classic CSS containment, but content-visibility goes a step further, because in addition to isolation it also allows skipping the rendering work entirely, not just containing it. For components that must always stay visible but need their rendering cost isolated from their surroundings, plain contain without content-visibility remains the right choice.

For images, content-visibility combines well with native loading="lazy": loading="lazy" delays the network loading of the image file, content-visibility: auto additionally delays the rendering work of the surrounding container. Both mechanisms complement each other because they act on different stages of the pipeline, network versus rendering, and together achieve the strongest effect for image heavy, long pages.

9. content-visibility in direct comparison

The following table contrasts the various approaches for hiding or deferring rendering work, to make the decision easier for a concrete project.

Approach Rendering saved State preserved Recommendation
display: none Yes No Only for actual removal from the document
visibility: hidden No Yes Only for pure hiding without a performance goal
content-visibility: hidden Yes Yes Tabs, accordions, modals with state
content-visibility: auto Yes, automatically Yes Long lists, feeds, documentation pages
Manual virtual scrolling Yes, including DOM removal Partial Very large data sets, high implementation cost

The most important difference in the table is between content-visibility: auto and manual virtual scrolling: virtual scrolling removes elements from the DOM completely, saving additional memory, but requires significantly more JavaScript logic and breaks standard browser features like find in page and fragment navigation unless implemented very carefully. content-visibility delivers most of the performance gain at a fraction of the complexity, because it is a native browser feature and needs no custom scroll simulation.

Mironsoft

CSS performance, rendering optimization and modern web frontends

Long pages that still scroll smoothly?

We analyze your long lists, feeds and documentation pages, identify rendering bottlenecks and implement content-visibility with correctly calibrated contain-intrinsic-size for measurably faster rendering.

Rendering audit

Performance trace analysis and identification of the most expensive layout regions

Implementation

Integrating content-visibility and contain-intrinsic-size cleanly into existing components

Measurement

Before and after comparisons with Lighthouse and Core Web Vitals

10. Summary

content-visibility is one of the most effective CSS properties for long pages, because it skips rendering work for invisible regions entirely instead of merely delaying it. The value auto activates this behavior automatically and reactivates rendering as soon as a region comes near the viewport. contain-intrinsic-size is not an optional extra here, it is necessary to avoid layout jumps, because otherwise the browser has no size information for unrendered elements.

Compared to display: none and visibility: hidden, content-visibility: hidden offers the best combination of state preservation and rendering savings for tabs and accordions. For long lists and feeds, content-visibility: auto is the right choice, with far less implementation effort than manual virtual scrolling, at a similar performance gain for most use cases. To prove the effect, measure with the Chrome DevTools performance panel and the layout shift API before and after the change.

content-visibility for long pages: the essentials at a glance

Core principle

content-visibility: auto skips layout and paint for regions outside the viewport, reactivates automatically as they approach.

Mandatory companion

contain-intrinsic-size: auto Xpx prevents layout jumps and stabilizes the scrollbar height.

State preservation

content-visibility: hidden keeps internal state, unlike display: none.

Browser features

Find in page, fragment anchors and keyboard navigation work automatically via the beforematch event.

11. FAQ: content-visibility for long pages

1What exactly does content-visibility do?
Skips layout, style and paint for invisible content. auto activates this automatically based on viewport proximity.
2Why also contain-intrinsic-size?
Without a placeholder size the element collapses to zero height, breaking the scrollbar and layout stability.
3Does Ctrl+F still work?
Yes, via the beforematch event the region is automatically rendered once the search finds a match there.
4Is it the same as display: none?
No. display: none discards state, content-visibility: hidden saves rendering while keeping state.
5How many elements make it worth it?
From about one hundred to two hundred similar elements the effect becomes clearly measurable in traces.
6Can it cause layout shifts?
Only with poor calibration. The auto keyword remembers the real last size and strongly reduces jumps.
7Apply to every child element?
No, only to sensible container units like cards or sections, not to every nesting level.
8Difference to virtual scrolling?
Virtual scrolling removes DOM nodes and saves memory but needs lots of JS. content-visibility is native and simpler.
9Which browsers support it?
Chrome and Edge since version 85, Firefox since 125, Safari since 18. The property is additive and rarely needs a fallback.
10How do I measure the effect?
DevTools performance panel before/after, PerformanceObserver for long-animation-frame and layout-shift, plus Lighthouse.