CLS is 0.35 and content jumps as the page loads. Enumerate the root causes and the fix for each.
A systematic account of what shifts layout and how to reserve space or avoid the shift entirely.
CLS accumulates every time visible content moves without a user action; each shift is scored by how much of the viewport moved and how far. The causes are a short, fixable list. (1) Media without dimensions — images, videos, iframes, embeds that have no reserved box collapse to zero then push content down when they load. Fix: set width/height attributes or CSS aspect-ratio so the box is reserved before bytes arrive. (2) Web fonts — the swap from fallback to web font changes text metrics and reflows. Fix: font-display: optional, or match fallback metrics with size-adjust/ascent-override, and preload the font. (3) Dynamically injected content above existing content — banners, cookie notices, ad slots, 'you have new messages' bars inserted at the top shove everything down. Fix: reserve space for known slots (min-height on the ad container), or inject below the fold / overlay instead of in-flow. (4) Actions that resize without space reserved — an accordion or 'read more' that expands is user-initiated (within 500ms of input it's excluded from CLS), but async expansions are not. (5) Animating layout properties — animating top/left/height moves surrounding content; animate transform/opacity instead (composited, no reflow, no shift). (6) Late-arriving data that changes sizes — skeletons should match the final content's dimensions so the real content drops in without resizing. The unifying principle: reserve the final space up front and never insert or grow in-flow content above what the user is looking at. Measure which element shifted using the LayoutShift entries' `sources` in DevTools to target the actual culprit rather than guessing.
Driving CLS below 0.1; auditing a page that visibly jumps during load.
<!-- Reserve the media box so nothing jumps when it loads -->
<img src="hero.webp" width="800" height="450" alt="…">
<div style="aspect-ratio: 16/9;"><iframe src="…"></iframe></div>
<!-- Reserve space for an async ad/slot instead of letting it push content -->
<div class="ad-slot" style="min-height:250px"></div>
<style>
/* Animate composited props: moves the element, not its neighbors */
.toast { transition: transform .2s, opacity .2s; }
/* NOT: top / height / margin, which reflow surrounding content */
</style>