Contain-Intrinsic-Size and Scroll Anchoring
contain-intrinsic-size gives a content-visibility-skipped subtree a placeholder size so it still reserves scroll height while its real content is not rendered; the auto keyword makes the browser remember each subtree’s last-rendered size and reuse it. Getting this estimate right is what prevents scrollbar jumps and scroll-anchoring shifts that surface as Cumulative Layout Shift. This guide covers the sizing mechanics and the CLS failure mode. It builds on Content-Visibility and Rendering Subtrees, part of Layout and Paint Optimization.
Why a Placeholder Size Is Needed
When content-visibility: auto skips a subtree — the same mechanism covered in Using content-visibility for Offscreen Content — its descendants are not laid out, so without an intrinsic size the box collapses to near-zero height. The document’s total scroll height then represents only the rendered sections. As the user scrolls and skipped sections render, the page grows, the scrollbar thumb resizes, and the viewport content jumps — the classic scrollbar lurch.
/* ❌ No reserved size: skipped sections collapse, scroll height unstable */
.chapter { content-visibility: auto; }
/* ✅ Reserve a height estimate so scroll height stays stable */
.chapter {
content-visibility: auto;
contain-intrinsic-size: auto 800px; /* placeholder until really rendered */
}
The two-value auto 800px form means: use the remembered last-rendered size if one exists, otherwise fall back to 800px. The single-axis or two-axis forms (contain-intrinsic-size: 320px 800px) set width and height explicitly when content is uniform.
The auto Keyword
Plain contain-intrinsic-size: 800px uses 800px every time the subtree is skipped, even after the browser has seen its real height. If the estimate is wrong, the page shifts each time a section is skipped and re-rendered. The auto keyword fixes this: after a subtree renders once, the browser stores its actual size and substitutes that for the placeholder on subsequent skips.
/* The browser records the real height after first render and reuses it */
.chapter {
content-visibility: auto;
contain-intrinsic-size: auto 800px; /* 800px only until first real render */
}
After the first pass, scroll height reflects true content heights, so scrolling back and forth no longer drifts.
Scroll Anchoring and CLS
Because content-visibility implies layout containment, each skipped subtree resizes in isolation, but the document above the anchor still reflows when its box changes. Browsers run scroll anchoring to keep the visual viewport stable when content above the scroll position changes size. A content-visibility subtree whose placeholder estimate differs from its real height changes size exactly when it renders — and if that happens above the current scroll position, scroll anchoring has to compensate. When the compensation is imperfect or the shift is below the anchor, the result is a visible jump that the Layout Instability API records as CLS.
[Layout Shift] chapter #7 rendered
├─ placeholder height: 800px (estimate)
├─ real height: 1180px
├─ delta: +380px above viewport
└─ score: 0.18 ← exceeds 0.1 CLS target, hadRecentInput: false
The cure is to make the estimate close to reality (or let auto learn it) so the delta is small or zero.
/* Before: fixed estimate far from real height → CLS on render */
.chapter { content-visibility: auto; contain-intrinsic-size: 800px; }
/* After: auto remembers true size; estimate only matters for first paint */
.chapter { content-visibility: auto; contain-intrinsic-size: auto 1100px; }
Pick the fallback from a measured median of your real content heights so even the first render shifts as little as possible.
Measuring the Shift
Attribute shifts to specific skipped subtrees with the Layout Instability API, reading sources to see which node moved.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue // ignore user-driven shifts
for (const s of entry.sources) {
console.log('shifted', s.node, 'by', entry.value)
}
}
}).observe({ type: 'layout-shift', buffered: true })
The full attribution workflow for these entries — distinguishing input-driven from layout-driven shifts and turning sources into actionable nodes — is in Debugging CLS with the Layout Instability API.
Verification Checklist
| Metric | Target | How measured |
|---|---|---|
| CLS from skipped-subtree render | ≤ 0.1 | Layout Instability API |
| Scroll height stability | No jump on scroll | Visual / scrollbar thumb |
| Placeholder-to-real delta | Near 0 after first render | contain-intrinsic-size: auto |
Why the Estimate Travels With the Component
The failure mode that undermines contain-intrinsic-size in practice is drift: the value is set once against today’s typical content, then a redesign changes the section’s real height and nobody updates the estimate. When the placeholder and the real height diverge, the scrollbar starts drifting again and scroll-anchoring can misfire, because the browser reserved the wrong amount of space for skipped content. The discipline that prevents this is to treat the intrinsic-size value as data that belongs to the component, not a magic constant in a global stylesheet — colocate it with the component, derive it from a representative measurement, and revisit it when the component’s layout changes.
Modern engines soften the problem with contain-intrinsic-size: auto <value>, which remembers the last rendered size and reuses it as the placeholder once the element has been on screen. This makes the initial estimate matter only until first render, after which the real measured height takes over and scrolling back past the section does not shift. On engines without auto support the static estimate is all you have, so it pays to pick it from the 75th-percentile height of real content rather than the smallest example — reserving slightly too much space costs nothing visible, while reserving too little reintroduces the shift the property was meant to eliminate. The interaction with scroll anchoring, and how the browser keeps the viewport stable as skipped content resolves, is the crux of getting long virtualized pages to feel solid rather than jumpy.
Frequently Asked Questions
Does contain-intrinsic-size do anything without content-visibility?
On its own contain-intrinsic-size only supplies an intrinsic size for a box that has size containment (or content-visibility that establishes it). Without containment there is nothing to substitute a placeholder for, so the property is effectively inert. Pair it with content-visibility: auto on the same element.
Why keep a fallback value when I use the auto keyword?
The auto keyword only has a remembered size after the subtree has rendered at least once. The fallback in contain-intrinsic-size: auto 800px is what the browser uses for the very first paint, before any measurement exists — so choose it from a measured median height to minimize the initial shift.
Can a wrong placeholder estimate cause CLS even below the fold?
Scroll anchoring only compensates for changes above the scroll position, so a bad estimate matters most there. A subtree that grows below the current anchor still records a layout-shift entry when it renders into or near the viewport, which is why the median-based fallback matters for every skipped section, not just the ones above.
How do I pick a good fallback height?
Instrument real content: measure the rendered height of each skipped section across representative data, take the median, and use that as the fallback. Feeding the estimate from live measurement keeps the placeholder-to-real delta near zero, so even the first render of a section shifts little.
Related Guides
- Using content-visibility for Offscreen Content — how the skip mechanism this page reserves size for actually works.
- Content-Visibility and Rendering Subtrees — the parent overview of rendering-subtree skipping.
- Using contain: layout to Isolate Reflow Scope — the containment primitive that lets a subtree resize without reflowing the page.
- Debugging CLS with the Layout Instability API — full attribution workflow for the shifts these placeholders cause.