Cumulative Layout Shift (CLS)
📐 Cumulative Layout Shift (CLS)
What Counts as a Layout Shift
CLS tracks unexpected layout shifts — a visible element moving between two rendered frames without a user causing it directly. A shift triggered by a user's own action (an accordion expanding on click, a menu opening on tap) isn't unexpected, and isn't counted against CLS.
How It's Calculated
Each shift's score is impact fraction × distance fraction — how much of the viewport was affected, multiplied by how far the shift moved relative to the viewport. The scores accumulate across the entire page session, which is exactly why it's "cumulative" rather than a single snapshot.
🔍 Common Causes
Images Without Dimensions
The single most common cause: the browser doesn't know an image's size until it downloads, reserves zero space for it, then the image "pops in" and shoves everything below it down.
Ads & Embeds Without Reserved Space
Same root problem as images, applied to third-party content — an iframe or ad slot whose size isn't known until it loads.
Web Fonts (FOIT/FOUT)
A fallback font renders first with different per-character metrics, then swaps to the custom web font once it loads — reflowing every line of text that used it.
Dynamically Injected Content
A banner or notification injected above existing content pushes everything below it down — very different from content injected below the fold or after a user's own action.
🛡️ Prevention: Reserve Space Up Front
Every fix here follows the same principle — tell the browser how much space something needs before it actually loads:
Explicit Dimensions for Images
No Reserved Space vs Reserved Space
No Dimensions Set
The browser collapses the image's space to zero height, then everything below jumps down the moment it loads.
width/height or aspect-ratio Set
The browser reserves the correct box immediately, so the image simply fills in — nothing else on the page ever moves.
💻 Coding Challenges
Challenge 1: Fix an Image Causing Layout Shift
Given <img src="banner.jpg"> with no dimensions, rewrite it to prevent layout shift, assuming the image's natural size is 1200×400.
Goal: Practice applying the simplest, most common CLS fix correctly.
Challenge 2: Classify Shifts as Expected or Unexpected
For each of (a) a cookie-consent banner appearing at the top of the page 3 seconds after load, and (b) an FAQ answer expanding below a question the user just clicked, state whether it counts toward CLS and why.
Goal: Practice applying the "user-initiated vs unexpected" distinction to realistic scenarios.
Challenge 3: Reserve Space for an Ad Slot
An ad network's embed code injects a 300×250 ad into a <div class="ad-container"> after a delay. Write the CSS needed to prevent this from causing a layout shift when the ad finally loads.
Goal: Practice applying the "reserve space up front" principle to third-party content.
Setting width: 100% on a responsive image without also setting aspect-ratio (or a height) silently reintroduces the exact problem the fix was meant to solve — the browser still can't reserve the correct height until the image's natural dimensions become known after it loads, since a percentage width alone says nothing about the image's proportions. Fluid images need width: 100% paired with an explicit aspect-ratio (matching the image's real proportions) for the browser to reserve accurate space immediately, not just a responsive width on its own.
🎯 What's Next
With visual stability covered, the next chapter turns to responsiveness during actual use: Interaction to Next Paint (INP) — what it measures, and how long tasks on the main thread hurt it.