awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

CLS: why layout shifts happen and how to stop them

Crux Cumulative Layout Shift scores the worst burst of unexpected movement — un-sized images, late ads, font swaps, dynamically injected banners — and the fix is always the same: reserve space before the bytes arrive.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 12 min

An article loads fast. The user starts reading the second paragraph. Then an ad slot above the article fills in and shoves everything down by 300 px. The user was mid-sentence; now they are mid-paragraph-of-the-wrong-article. They did not tap anything. The page moved by itself. That is CLS — and it is the easiest of the three vitals to prevent once you understand the one rule behind all four classic causes.

How the score is calculated.

Each time a visible element changes position between two frames without a user-initiated cause, the browser records a layout shift. One shift’s score is:

impact fraction × distance fraction

Impact fraction: the combined area of all shifted elements, as a fraction of the viewport. If an image that covers half the viewport jumps, impact fraction is 0.5. Distance fraction: the largest distance any element moved, as a fraction of the viewport height. A shift of 20% of viewport height gives 0.2.

So a single shift that moves a half-viewport element by 20% of viewport height scores 0.5 × 0.2 = 0.1 — exactly on the “good” border.

CLS is not a lifetime sum of all shifts. It is the sum of shifts in the worst session window: a cluster of shifts where each shift is within 1 second of the previous, and the entire cluster spans at most 5 seconds. This change from the original algorithm was deliberate — the old sum unfairly penalised long-lived pages and infinite scroll.

CLS scoring rules
Shift score
impact fraction × distance fraction
CLS reported
worst 5 s session window sum
Shift exclusion window after input
500 ms
Good threshold (p75)
≤0.1

The 500 ms exclusion — what CLS does not punish.

Shifts within 500 ms of a user interaction are excluded. Opening an accordion, expanding a dropdown, clicking a “show more” button — the resulting movement is expected by the user and does not count toward CLS. CLS only punishes movement the user did not cause and did not expect.

Four classic causes and their fixes.

  1. Images without dimensions. An <img> with no width and height attributes has a 0-height box until the bytes arrive. When the image loads, the browser learns its intrinsic size, reruns layout, and every element below jumps down. Fix: always set width and height HTML attributes (modern browsers derive aspect-ratio from them automatically and reserve a correctly-proportioned box). CSS can still make the image fluid with width: 100%; height: auto — the attributes supply the ratio, the CSS supplies responsive sizing.

  2. Ads, embeds, and iframes injected into unreserved space. An ad slot that renders 250 px of content into a container with no reserved height shoves everything below it down. Fix: wrap every ad and embed slot in a container with a min-height equal to the largest expected ad creative.

  3. Web font reflow. A fallback font with different line metrics renders first; when the web font loads, the browser reflows text — characters are wider or narrower, lines rebreak, elements below shift. Fix: use size-adjust and the ascent-override / descent-override font descriptors to make the fallback metrics match the web font; or use font-display: optional to only apply the web font on repeat visits.

  4. Dynamically injected content above existing content. A cookie banner, notification bar, or chat widget inserted above the page body at runtime pushes everything down. Fix: insert it into pre-reserved space (a container with a known height in the layout), or render it as an overlay (fixed/absolute) so it does not participate in flow layout at all.

The unifying principle: reserve space for anything whose final size is not known at parse time, or ensure it never participates in flow layout.

Animation pitfall.

Animating layout properties — top, left, height, width, margindoes generate layout shifts and can hurt CLS, even if the movement looks intentional to a developer. The fix is to animate transform instead (translateY, scale), which is composited and never triggers layout. A shift caused by a CSS animation is still a shift unless it follows a user interaction within 500 ms.

Why this works

The session window model replaced the original “lifetime sum” model because long-lived pages and infinite scroll were penalised for shifts that happened five minutes after load — well outside the user’s reading context. The session window focuses CLS on bursts of bad behavior: a cluster of shifts during an ad reload, or a batch of images loading without dimensions, rather than the accumulated cost of a site someone browses for an hour. CLS is now more representative of what a user actually notices.

Reserve space for a late-loading image to eliminate CLS

1/3
Quiz

A user opens an accordion and the content below it moves down. Does this count toward CLS?

Quiz

A cookie banner is injected above the article body after the page loads, pushing all content down 60 px. What is the correct CLS fix?

Recall before you leave
  1. 01
    How is one layout shift's score calculated, and what is the CLS session window?
  2. 02
    Name the four classic CLS causes and the fix for each.
  3. 03
    A CSS animation moves an element using 'top' and the page fails its CLS audit. How do you fix it without removing the animation?
Recap

CLS scores the worst burst of unexpected layout movement: one shift’s score is impact fraction × distance fraction, and CLS reports the worst 5-second session window. Shifts within 500 ms of a user interaction are excluded — CLS only punishes movement the user did not cause. The four classic causes are images without dimensions, ads and embeds in containers with no reserved height, web font reflow when the fallback metrics differ, and dynamically injected content above existing flow. The fix for all four follows one rule: anything whose final size is unknown at parse time must have space reserved before it arrives, or it must use overlay positioning so it does not participate in flow layout. Animating layout properties (top, left, height) generates shifts — animate transform instead.

Connected lessons
appears again in143
Continue the climb ↑Lab vs field: why the two disagree and how to use each
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.