How to fix layout shift
CLS measures how much your page moves while loading. Here's what causes it and how to reserve space so nothing jumps.
Why the page jumps
A layout shift happens whenever something already on screen moves to a different position without the visitor causing it. The browser scores each shift by how much of the viewport moved and how far, and CLS is the worst burst of shifts in any five-second window.
The cost is not aesthetic. It is the moment you go to tap “Continue”, an image finishes loading above it, and you tap “Delete account” instead.
The five causes, in order of how often they turn up
1. Images and video with no dimensions
Without width and height, the browser reserves nothing and everything below snaps down when the file arrives. Set the intrinsic pixel dimensions as attributes and let CSS handle the display size — modern browsers derive an aspect ratio from the attributes and reserve the right box.
<img src="/photo.jpg" width="1600" height="900" alt="…">
img { width: 100%; height: auto; } /* CSS still controls display size */2. Ads, embeds and iframes
Ad slots are the single biggest source of CLS on content sites, because the ad’s height is not known until it arrives — and sometimes it does not arrive at all. Reserve the largest size the slot can serve with min-height, and never collapse an unfilled slot to zero. A visible empty space is worth more than a page that jumps.
3. Web fonts swapping
A fallback font with different metrics reflows every line of text when the real font arrives. size-adjust, ascent-override and descent-override on a @font-face fallback let you match the metrics closely enough that the swap moves nothing.
4. Content injected above existing content
Cookie banners, promo bars, “you have items in your basket” strips. If it is inserted at the top of the DOM after paint, everything shifts. Either render it in the initial HTML, or overlay it with position: fixed so it takes no space in the flow.
5. Animating layout properties
Animating height, top, margin or width forces a re-layout on every frame, and each one is a shift. Use transform instead — it runs on the compositor and never touches layout.
Finding the actual culprit
- Open Chrome DevTools, Performance panel, tick Screenshots, record a reload with the network throttled to Slow 4G. The Experience track marks each shift; click one and it names the element that moved.
- Or paste this in the console and reload — it logs every shift with the nodes involved:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) console.log(entry.value, entry.sources);
}
}).observe({ type: "layout-shift", buffered: true });Do this on a phone-sized viewport with an empty cache. CLS on desktop is often zero while mobile is a mess, because the narrow layout stacks things that sat side by side.
One thing to be careful about
Shifts within 500ms of a real interaction are excluded from the score, because expanding an accordion is expected. That is a scoring rule, not permission — an unexpected jump still costs you the sale even when it does not cost you the metric. And if your CLS is good but your LCP is bad, fix LCP first: it is the one that decides whether people stay long enough to be annoyed.
Common questions
- What counts as a good CLS?
- 0.1 or below at the 75th percentile. Up to 0.25 needs work; above that is poor. Unlike the other vitals it has no unit — it is a score combining how much of the screen moved with how far it moved.
- My CLS is 0 in the lab but bad in the field. How?
- Lab tests only watch the first few seconds and never scroll, click or accept a cookie banner. Real visitors do all of those. Shifts caused by lazy-loaded images further down the page, by a consent banner collapsing, or by content injected after an interaction show up only in field data.
- Do animations count as layout shift?
- Only if they move other content. A transform or opacity animation is composited and costs nothing. Animating width, height, top or margin re-runs layout every frame and does count. Rule of thumb: animate transform and opacity, nothing else.
- Does a sticky header cause CLS?
- Not by itself, but a header that becomes sticky after scroll and changes height at the same moment does. So does one that only appears once JavaScript has run, because everything below it jumps up.
Check a page against this
Enter any public URL. You’ll get the one change that recovers the most load time, what it’s worth, and the measurements underneath.
Other guides
- How to fix a slow LCP — Your main image or headline takes too long to appear.
- How to fix slow INP — Taps and clicks take a moment to do anything.
- How to fix a slow server response — Your server is slow to start replying at all.
- How to eliminate render-blocking CSS and JS — CSS and JS files stop anything appearing on screen.
- Why you're seeing "no field data" — Google has no real-visitor data for your site yet.