How to Use contain-intrinsic-size to Stop Scrollbar Jumps
This guide is part of CSS Containment & content-visibility, within Rendering & CSS Performance. When content-visibility: auto skips rendering a section, the browser needs to know how much space that section takes; otherwise it would treat it as zero height. contain-intrinsic-size provides that placeholder size. If the placeholder is far from the real size, the page's total height changes as sections render during scrolling. The scrollbar thumb jumps, the page may lurch, anchor links land in the wrong place, and scroll-linked UI misbehaves.
Getting placeholder sizes right — and using the auto keyword so the browser remembers real sizes after rendering — removes nearly all of these problems while keeping the rendering savings.
Rapid Diagnosis
- Watch the scrollbar thumb while scrolling a page with
content-visibility. Jumps or size changes mean placeholder sizes are wrong. - Test anchor links to late sections; landing above or below the target indicates size drift above it.
- Inspect sections in DevTools: compare the rendered height with the
contain-intrinsic-sizevalue. - Check for CLS reports while scrolling in lab tools with scroll interactions.
Root Cause Analysis
1. No intrinsic size. Skipped sections collapse to zero.
2. One estimate for all sections. Real heights vary widely between sections.
3. Fixed values without auto. Sections revert to the estimate when skipped again.
4. Responsive layouts. Heights differ greatly between mobile and desktop.
Step-by-Step Resolution
1. Always use auto with an estimate
.section {
content-visibility: auto;
contain-intrinsic-size: auto 800px; /* auto = remember last rendered size */
}
/* trade-off: remembered sizes only exist after a section has rendered once, so
the first scroll through the page still depends on the estimate. */
2. Use per-breakpoint estimates
.section { contain-intrinsic-size: auto 1400px; } /* mobile: tall */
@media (min-width: 900px) { .section { contain-intrinsic-size: auto 800px; } }
3. Generate per-section estimates when sizes vary greatly
For content with very different section lengths, compute an estimate per section at build or render time (for example from word count and image count) and set it inline.
// Build step: estimate height from content, emit as a custom property.
const est = Math.round(120 + words * 0.42 + images * 360); // px, mobile profile
html = `<section style="--est:${est}px" class="section">…</section>`;
// CSS: .section { contain-intrinsic-size: auto var(--est); }
// trade-off: the heuristic needs tuning per template; measure real heights once
// and fit the coefficients rather than guessing.
4. Use separate width and height when needed
contain-intrinsic-size accepts width and height (auto 300px auto 800px); for block-level sections only the height usually matters, but for inline-size-contained elements set both.
Verification
Scroll from top to bottom quickly, then back up, watching the scrollbar thumb; it should remain nearly constant in size. Follow anchor links to the last sections and check they land at the target. In DevTools, compare the computed height of skipped sections (the estimate) with rendered heights. Record a scroll in the Performance panel with Layout Shift Regions enabled to confirm no unexpected shifts.
Worked Example: A Forum Thread Page
A forum rendered threads with up to 500 posts, applying content-visibility: auto to each post with contain-intrinsic-size: 200px. Posts varied from one line to several screens with images. Users reported that the scrollbar "kept moving" and that "jump to last unread" links landed in random places. The team switched to auto with an estimate computed per post from text length and attachment count, emitted as a CSS custom property. Anchor accuracy improved to within one post, scrollbar jumps disappeared after the first scroll, and the rendering savings stayed the same: thread pages kept a 70% reduction in initial layout time.
How auto Remembers Sizes
With auto, when an element with size containment from content-visibility has been rendered, the browser records its last rendered size. When it is skipped again, that recorded size is used instead of the estimate. This happens per element and lasts for the life of the element in the document. If the element is removed and recreated (common in client-rendered lists that re-render), the remembered size is lost. In frameworks, prefer stable keys so elements persist across updates. Remembered sizes also reset across navigations, which is why the initial estimate still matters for anchor links followed from other pages.
Measuring Real Sizes to Calibrate Estimates
Rather than guessing, measure. Load representative pages, let every section render (scroll to the bottom), and record each section's height with a ResizeObserver, along with simple features of its content: word count, number of images, number of code blocks. Fit a simple linear estimate from those features per template and breakpoint. Twenty or thirty pages per template are usually enough. Re-run the calibration when the design changes typography or spacing, since heights shift with every change to line height or margins.
Common Mistakes
- Omitting auto. Sections snap back to the estimate when skipped again.
- Tiny estimates. Make the document shorter than reality and push anchors upwards.
- One size for all breakpoints. Mobile sections may be twice as tall as desktop ones.
- Re-creating elements on every render. Remembered sizes are lost.
Edge Cases
Horizontal layouts. In horizontal scrollers, the width estimate matters; set both dimensions.
Images without dimensions inside sections. Real sizes change after images load; set image dimensions to keep remembered sizes accurate.
Dynamic content growth. Sections that expand (comments loading) update their remembered size after re-rendering.
Container queries. Size containment interacts with container queries; check that container-type and content-visibility are not on the same element unless intended.
FAQ
What does contain-intrinsic-size do?
It sets the size an element uses for layout when size containment applies — for example, when content-visibility: auto skips its contents.
What does the auto keyword do?
It tells the browser to remember the element's last rendered size and use it in place of the estimate after the element has been rendered once.
How accurate does the estimate need to be?
Within a factor of about two avoids most visible jumps on first scroll; per-section estimates are needed for accurate anchor links on very long pages.
Does a wrong estimate cause CLS?
Size changes of offscreen content generally do not count as layout shifts of visible elements, but they can move the scroll position and visible content in some cases. Accurate estimates minimise the risk.
Can I set contain-intrinsic-size without content-visibility?
Yes, with contain: size, but then the element always uses the given size. The common use is together with content-visibility: auto.
Is there a JavaScript API to read remembered sizes?
No direct API exists. You can measure rendered sizes with ResizeObserver and store them yourself if you need them across navigations.
Why do anchor links still land slightly off on first visit?
Sections above the target have not rendered yet, so the browser uses their estimates. Per-section estimates generated from content reduce this error.
Do I need different estimates for dark mode or font sizes?
Usually not for colour themes, but larger user font sizes make sections taller. Estimates in em or based on line counts scale better than fixed pixel values.
Related
- Using content-visibility: auto on long pages — where to apply the property.
- Fixing CLS from skeleton screens that resize — another placeholder sizing problem.
- Isolating widgets with CSS contain — containment without skipping.