# Speed up page rendering by setting content-visibility to auto

The CSS declaration `content-visibility: auto` lets the browser skip rendering
an element’s contents until they’re near the viewport or otherwise needed.
Calling it "lazy rendering" might be a helpful analogy.

When this article was first published, `content-visibility` was only supported
in Chromium-based browsers, starting with version 85. It now works in current
Chrome, Edge, Firefox, and Safari. See
[MDN’s browser compatibility table](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/content-visibility#browser_compatibility)
for details.

## Content visibility demo

Here’s a demo of a simple landing page with cat pictures:

[A demo page which uses content-visibility.](https://codesandbox.io/s/7ic2m)

There’s not much to see; the difference is under the hood. Compare recordings in
your browser’s Performance panel with the declaration enabled and disabled to
see how much rendering work it saves.

## How does "content-visibility: auto" actually work?

Browsers are generally fast at rendering HTML (it’s their 9-to-5 job), but they
still break a sweat on sites packed with complex layouts and visual effects.

But why spend time laying out a module at the bottom of the page when you’re at
the top? Off-screen content can still affect the rest of the layout, and its
descendants might be positioned on-screen. Browsers can render a page
progressively, but they can’t simply ignore everything outside the viewport.

`content-visibility: auto` applies containment to a section, limiting how its
contents affect the surrounding page. This lets the browser skip much of the
section’s rendering work while it’s off-screen. Rendering resumes as the section
approaches the viewport, or when its contents become relevant through focus or
selection, for example.

We’re all more or less familiar with image lazy loading, right? The analogy is
useful, but this defers rendering work, not resource downloads. It doesn’t
replace `loading="lazy"` on images or stop JavaScript from running.

The contents remain in the DOM and available to in-page search and keyboard
navigation. [web.dev’s explanation](https://web.dev/articles/content-visibility)
goes into more detail. Early Chrome versions had a screen reader bug, documented
in the [accessibility issues](#accessibility-issues) section below.

## The code

Start with two CSS declarations:

```css
.module {
  content-visibility: auto;
  contain-intrinsic-size: auto 0 auto 500px;
}
```

While the contents are skipped, the browser sizes the element as though it were
empty. Without a specified height or a placeholder, an ordinary block can
collapse to zero content height.

[`contain-intrinsic-size`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/contain-intrinsic-size)
provides that placeholder. Here, `auto 0` sets a fallback content width of zero,
and `auto 500px` sets an estimated content height of 500 pixels. In normal block
layout, the module’s automatic width still fills the available space.

Each `auto` tells the browser to reuse the last rendered size in that dimension
when available. The original example used `0 500px`, without explicitly asking
the browser to remember the size.

These are intrinsic content dimensions, not a fixed outer size: padding and
borders still apply, and the contents determine the height when rendered.

The three main values for **`content-visibility`** are:

visible
  The default: the contents are laid out and painted normally.
  hidden

The contents are skipped, including for in-page search and keyboard
    navigation. Unlike setting `display: none` on the element, its
    own box still participates in layout. Steve Faulkner’s post "[Short note on
    content-visibility:
    hidden](https://html5accessibility.com/stuff/2020/08/25/short-note-on-content-visibility-hidden/)"
    explores the early implementation’s accessibility behavior.

auto

Applies layout, style, and paint containment. The browser can skip rendering
    the contents when they aren’t relevant to the user, adding size containment
    while they’re skipped. Unlike `hidden`, this keeps the contents
    available to browser features such as in-page search.

## Some gotchas

There are a few tradeoffs to keep in mind.

### Jumpy scrollbar

As you scroll, the scrollbar can jump or change size as placeholder heights are
replaced with actual heights. The closer `contain-intrinsic-size` is to the
rendered content size, the smaller the adjustment. Getting the estimate right
can be tricky on responsive sites, where text wrapping changes the height.

The `auto` values help on subsequent visits to a section by reusing its measured
size, but the first render still relies on your estimate. I’ll take a small
scrollbar adjustment for a useful speedup, though it’s worth checking the result
at different viewport widths.

### Containment changes layout behavior

Paint containment clips overflowing descendants, and layout containment changes
the containing block for absolutely and fixed-positioned descendants. Check
menus, tooltips, and other elements that need to escape a section’s bounds
before applying this broadly. These effects follow from the
[CSS containment rules](https://drafts.csswg.org/css-contain-2/#containment-layout).

Also, JavaScript that measures skipped descendants can force the browser to do
the rendering work you were trying to avoid.

### Accessibility issues

Historical note: the heading visibility bug described below has been fixed.

The original concern was about headings. Marcy Sutton investigated it:

> Neat. But my spidey accessibility senses went off as soon as I read those
> words: **off-screen** and **rendering**. [...] My initial thought was that
> `content-visibility: auto` could pose an access problem on initial load by
> suppressing part of the page outline from screen readers, so I set out to test
> it.
> [Marcy Sutton](https://marcysutton.com/content-visibility-accessible-semantics)

In Chrome 85–89, headings inside skipped sections could be missing from screen
reader navigation, including NVDA’s Elements List. Users had to scroll through
the page before the full heading structure became available.

The workaround was to keep headings and landmarks outside sections using
`content-visibility: auto`. That recommendation addressed the old bug; it is not
a general restriction on using the property today. Marcy’s article now documents
the fix.

There is a separate caveat: skipped descendants that are intentionally hidden
with `display: none` or `visibility: hidden` can still appear in the
accessibility tree if their styles haven’t been evaluated. See
[MDN’s accessibility notes](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/content-visibility#accessibility).
Test with keyboard navigation and screen readers, especially if a section mixes
visible and intentionally hidden content.

## Conclusions

`content-visibility: auto` can reduce rendering work on long pages with complex
sections. Pair it with a reasonable placeholder size, then measure the effect on
your page.

A cool side effect can be smoother window resizing, because there’s less
off-screen layout for the browser to calculate.
