Fixing Cumulative Layout Shift (CLS): the usual culprits
CLS measures how much visible content jumps around unexpectedly. Almost every layout shift comes from content arriving without reserved space: images without dimensions, ads, embeds, late banners and web fonts.

Everyone has experienced it: you start reading, an ad loads above the paragraph and the text jumps down; you go to tap a link and a banner slides in so you tap something else. Cumulative Layout Shift is the Core Web Vital that measures this. It is the easiest of the three to understand and, in most cases, the easiest to fix, because almost every shift has the same root cause: content that arrives after the initial render without space reserved for it.
How CLS is calculated
Each time a visible element moves from one rendered frame to the next without a user interaction causing it, the browser records a layout shift score. The score combines two things:
- Impact fraction: how much of the viewport was affected by the moving elements (their area before and after the move).
- Distance fraction: how far they moved, relative to the viewport's largest dimension.
Shifts are grouped into session windows: bursts of shifts with less than one second between them, capped at five seconds per window. CLS is the score of the worst window during the visit. This design means a page that loads with a small jump is not penalised for every subsequent minor shift, but a single big jump, or a cluster of them, sets the score.
The good threshold is 0.1 at the 75th percentile. To give a sense of scale, a banner that pushes down content filling half the screen by a quarter of the screen height scores roughly 0.5 × 0.25 = 0.125 on its own, already over the threshold.
Finding the elements that move
- Search Console's Core Web Vitals report lists URL groups with poor CLS. Start with the group affecting the most URLs.
- PageSpeed Insights lists the largest layout shifts during a lab load and highlights the elements involved.
- Chrome DevTools: the Performance panel marks layout shifts on the timeline; selecting one highlights the moved nodes. The Rendering panel has a "Layout Shift Regions" option that flashes shifting areas while you use the page.
- Real visits: the web-vitals library can attribute the largest shift to a specific element, which catches shifts that only happen for some users, such as those who see a particular ad size or a consent banner.
Test on a phone-sized viewport and scroll the page slowly to the bottom. Many shifts happen below the fold as lazy-loaded content arrives.

Culprit 1: images and videos without dimensions
An img without width and height attributes takes zero height until it loads, then pushes everything below it down. This is the most common cause of CLS and the easiest to fix:
<img src="/img/kitchen.webp" alt="Open-plan kitchen with oak cabinets" width="1200" height="800">
Modern browsers use the two attributes to calculate the aspect ratio and reserve the right space even when CSS makes the image responsive with width: 100%; height: auto. For containers with unknown content, CSS aspect-ratio does the same job. Video elements and iframes need the same treatment. The image SEO guide covers the rest of the image markup.
Culprit 2: ads, embeds and iframes
Ad slots that collapse to zero and then expand when an ad arrives are a major source of shifts on publisher sites. Embeds from social networks and video platforms do the same, often resizing several times as they load.
- Reserve space for every ad slot with a
min-heightbased on the most common ad size for that slot. A little empty space when no ad fills is far better than a jump. - Avoid placing ad slots at the very top of the content area, where a shift moves everything users are reading.
- Do not collapse empty slots after the page has rendered; leave the reserved space or show a placeholder.
- Wrap embeds in a container with a fixed aspect ratio or minimum height.
Culprit 3: banners and notices injected at the top
Cookie banners, promotional bars ("free shipping this week"), app install prompts and notices inserted above existing content push the whole page down. Two fixes:
- Render the banner in the initial HTML so the space is part of the first layout, rather than inserting it with JavaScript a second later.
- Or overlay it with fixed or sticky positioning at the bottom of the screen, so it covers content instead of moving it. Overlays do not cause layout shifts.
Keep overlays modest in size on mobile; a banner that covers most of the screen avoids CLS but creates a different user experience problem.
Culprit 4: web fonts
When a web font loads and replaces the fallback font, text often changes width and height, reflowing paragraphs and moving everything below. Options:
- Preload the main font files so they arrive before first render more often.
- Match fallback metrics: the CSS descriptors
size-adjust,ascent-overrideanddescent-overrideon a fallback@font-facelet you tune a system font to take the same space as your web font, so the swap causes no reflow. - Use
font-display: optionalfor body text if you can accept the fallback on slow connections; the browser only uses the web font if it is available almost immediately, and never swaps later. - Use fewer font files. Every weight and style is another file that can arrive late.
Culprit 5: content loaded after render
Product recommendations, reviews widgets, "related articles", personalised blocks and price or stock updates fetched by JavaScript all arrive after the first paint. If they are inserted above content the user can see, everything moves.
Reserve their space with a skeleton placeholder of the right height, insert them below the viewport, or render them on the server. For e-commerce, product prices and stock that change after load also cause small shifts in tight layouts; a fixed-width price area prevents that.
Culprit 6: animations of layout properties
Animating top, left, height, width or margin changes layout on every frame. Use transform (translate, scale) and opacity instead; they are handled by the compositor and do not count as layout shifts. Carousels and sliders that animate with left are a frequent hidden source.
Infinite scroll, "load more" and sticky headers
Lists that append items as the user scrolls rarely cause shifts, because new items are added below what the user is looking at. Problems appear when something is inserted above: a "new items available" notice at the top of a feed, or a footer that is visible briefly and then pushed down by freshly loaded items, taking whatever the user was about to tap with it. Keep the footer out of reach of infinite lists, or switch to a "load more" button, which counts as a user interaction.
Sticky headers that change height while scrolling, for example shrinking from a tall logo bar to a compact one, also shift content if the change affects layout. Animate the shrink with transform, or keep the header's layout height constant and only change what is drawn inside it.
Back/forward cache and CLS
When a user navigates back to a page, browsers can restore it instantly from the back/forward cache, with no layout shift at all. Pages that are not eligible, often because of unload event handlers or certain cache headers, reload from scratch and repeat all their shifts. Chrome DevTools has a back/forward cache test under the Application panel that lists reasons a page is not eligible. Fixing those improves CLS and speed on return visits.
A quick reference
| Cause | Fix |
|---|---|
| Images, videos, iframes without dimensions | width and height attributes, or CSS aspect-ratio |
| Ads and embeds | Reserved min-height per slot; no collapsing |
| Top banners inserted late | Render in HTML, or overlay with fixed positioning |
| Web fonts swapping | Preload, metric-matched fallbacks, font-display optional |
| Late dynamic content | Skeletons with fixed height, insert below viewport |
| Animations | transform and opacity only |
After fixing, verify in the lab by loading and scrolling with layout shift regions highlighted, then watch field data in Search Console over the next 28 days. Shifts are also worth checking whenever a template changes: a new promotional bar or a redesigned header can undo months of work. The LCP and INP guides cover the other two Core Web Vitals, and the SEO checker covers the technical basics around them.
Questions
What is a good CLS score?
0.1 or less at the 75th percentile of real page loads is good. Between 0.1 and 0.25 needs improvement; above 0.25 is poor. CLS has no unit; it combines how much of the viewport moved and how far.
Do shifts caused by user clicks count?
Shifts that happen within 500 milliseconds after a user interaction, such as expanding an accordion, are excluded. Shifts that happen on their own, or long after the click, count.
Why is CLS fine in Lighthouse but poor in the field?
Lighthouse measures only the initial load without scrolling. Real users scroll, wait for ads, and interact, and CLS is measured over the whole visit in session windows. Shifts from lazy-loaded ads or late banners often appear only in field data.
Does animation cause CLS?
Animations using CSS transform do not, because they do not change layout. Animating properties such as top, height or margin does cause layout shifts.
Keep reading

Fixing Largest Contentful Paint (LCP): a step-by-step diagnosis
LCP measures when the biggest thing on screen appears. Every slow LCP breaks down into four phases, and once you know which phase is slow,…

Fixing Interaction to Next Paint (INP): finding the slow handlers
INP replaced First Input Delay as a Core Web Vital in 2024 and is much harder to pass. Most poor INP comes from a few slow interactions cau…

Image SEO: file names, alt text, formats, sizes and lazy loading
Images are one of the biggest drivers of page weight and one of the most neglected sources of search traffic. Get the HTML right, serve mod…