CLS: Layout Stability & Compositor Cooperation
CLS is not about adding width and height attributes — it is about giving the browser's layout engine a reserved geometry contract before paint, so elements never shift reserved space, only fill it.
Modern Web Rendering & Performance
CLS: Layout Stability & Compositor Cooperation
Layout shift is not a visual quirk. It is an engineering failure at the geometry contract layer — the browser's layout engine did not know, before painting an element, how much space that element would occupy. When the element's actual size is discovered (image loads, font swaps, dynamic content injected), the engine recalculates geometry for every element that was displaced, and the visible result is a shift. The metric captures this as impact fraction × distance fraction — area × displacement — and sums it across the entire session.
The fix is never "stop animating." The fix is a geometry reservation: tell the layout engine, before paint, exactly how much space this element will occupy. aspect-ratio, min-height, font-display: optional, size-adjust — these are all geometry contracts, not style properties. Understanding CLS means understanding what information the layout engine needs before it can paint without shifting.
1. The Broken Pattern: Adding width/height and Still Failing CLS
Engineers fix image dimensions and cannot understand why CLS remains at 0.19. The image was not the problem. The dynamically injected content above the fold was.
2. transform Animations vs. Positional Property Animations
This is the most common SDE-1 CLS surprise — and the most important rule to learn first:
Properties that run on the compositor thread (transform, opacity, clip-path, filter) never trigger layout recalculation and cannot cause CLS. Properties that change an element's geometry in the document flow (top, left, margin, padding, width, height, border, font-size) always trigger layout and always risk CLS if they shift other elements.
3. CLS Defined: Impact Fraction × Distance Fraction
Thresholds:
| Score | Threshold |
|---|---|
| ✅ Good | ≤ 0.1 |
| ⚠️ Needs Improvement | ≤ 0.25 |
| ❌ Poor | > 0.25 |
The hadRecentInput exclusion is significant: layout shifts caused by the user scrolling, tapping, or clicking within the last 500ms do not count toward CLS. Without filtering for this, scroll-driven animations and interactive expansions would make CLS meaningless.
4. Layout Shift Causes and Fixes
4.1 Images Without Reserved Dimensions
Why aspect-ratio is more robust than fixed width/height for responsive layouts:
4.2 Dynamic Content Insertion
For ads and third-party embeds — the #1 real-world CLS source:
5. Font-Induced CLS
Web fonts cause CLS when the fallback font and the web font have different line heights, letter-spacing, or character widths — causing text blocks to reflow when the web font loads.
5.1 font-display Tradeoffs
5.2 size-adjust for Zero-CLS Font Swap
size-adjust is a @font-face descriptor that scales the fallback font's glyphs to match the web font's metrics — eliminating the layout reflow without hiding the text:
6. Skeleton Placeholders and the Dimension Match Requirement
Skeleton screens are a common CLS mitigation — but only if the skeleton's dimensions exactly match the loaded content:
7. Diagnosing CLS in Production
In production, send the sources[0].node selector (built via getSelector(node)) to your analytics — it tells you exactly which element shifted and where it moved from/to, enabling precise attribution without needing a user reproduction.
Summary
| Concept | Rule |
|---|---|
transform vs. positional |
transform, opacity, clip-path run on the compositor — no CLS. top, left, margin, height trigger layout — always risk CLS. |
| Score formula | Impact fraction (viewport area affected) × distance fraction (distance moved / viewport height). Sums per session, excluding hadRecentInput shifts. |
aspect-ratio |
More robust than fixed width/height for responsive images — reserves layout space proportionally at any container width. |
font-display: optional |
Zero CLS — web font used only if cached; fallback used otherwise. Pair with size-adjust on the fallback @font-face to match metrics when swap is required. |
| Dynamic insertion | Always pre-reserve the slot before injecting. Ads, cookie banners, and lazy-loaded content must fill a pre-sized container, not push content downward. |
| Skeleton precision | Skeleton dimensions must match loaded content dimensions exactly. A skeleton that collapses on load causes a shift, not prevents one. |
What's Next
In Part 9, we close the series with the React Compiler — the opt-in babel/SWC plugin that automates memoization across your entire component tree, and why your job as an engineer shifts from writing
useMemoto ensuring component purity. Part 9: The React Compiler →
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.