How to fix Cumulative Layout Shift, step by step
A practical recipe for a CLS above 0.1: tell load shifts from later ones, find what moved, and fix images, embeds, fonts, banners and late content.
In this guide
Step 1: is it a load shift or a later shift?
Open the page in PageSpeed Insights and compare two numbers; what CLS measures explains how the score works. The real-user CLS is in the top section, and the lab CLS is in the performance metrics lower down.
- Both are above 0.1. The cause is in the page load, and the lab test can show you what shifted.
- The real-user value is much worse than the lab value. The shifts happen after the load: while scrolling, when lazy content arrives, when a banner appears after a delay, or in an iframe. A lab test cannot see them, so you have to watch for them.
- Both are good. You pass. Check back in a few months.
Step 2: find what moved
For load shifts, use the report. In Lighthouse 13 the "Layout shift culprits" insight lists the biggest shifts, and for each it names causes it can recognize: an image without a size, a web font that loaded, an iframe that was injected, an animation. A separate check flags images without width and height. The element it lists is usually the one that moved, not the one that pushed it. If a paragraph is listed, look at what sits above it.
For anything else, watch it happen. In Chrome DevTools, record a page load in the Performance panel and look at the Experience track: each shift is marked, and selecting one shows the score and the rectangles of the elements that moved. For shifts that happen while scrolling, record while you scroll and use the page the way a visitor does. DevTools can also highlight layout shift regions while you browse.
For shifts you cannot reproduce, measure real visitors. The web-vitals library has an attribution build that reports the element that shifted the most (largestShiftTarget), when it shifted and by how much, and the load state at that moment. Send it to your analytics for a few weeks and the same few elements will show up again and again.
Step 3: apply the fix that matches
| What moved | Usual cause | Fix |
|---|---|---|
| Text below an image | Image without dimensions | Fix 1 |
| Text below a video or embed | Embed with no reserved height | Fix 2 |
| The whole page after a short delay | Banner, bar or pop-up pushed it | Fix 3 |
| Text lines, buttons, the header | Web font swap | Fix 4 |
| A block below a widget | Late content with no placeholder | Fix 5 |
| Something that slides or jumps | Animation with layout properties | Fix 6 |
Fix 1: give every image its size
Add width and height attributes that match the image file's own dimensions. The browser turns them into an aspect ratio before the file arrives, and your CSS can still make the image fluid:
<img src="/img/team.jpg" width="800" height="600" alt="The team at the front desk">img { max-width: 100%; height: auto; }The height: auto is what keeps the shape correct when CSS changes the width. The image optimization guide covers sizes and formats. For images in a container that does not have intrinsic proportions, such as a hero area built from a background image, give the container an aspect-ratio or a min-height. If your CMS or page builder outputs images without sizes, check its image settings, or its filters, before editing every page by hand: WordPress, for example, adds width and height to the images it generates in the editor, and the missing ones are usually from the theme or from plugins.
Fix 2: reserve the box for embeds and iframes
A video, map or social post is an iframe that has no height until it loads. Put it in a box with a fixed shape:
.embed { aspect-ratio: 16 / 9; width: 100%; }
.embed iframe { width: 100%; height: 100%; border: 0; }For widgets with a content-dependent height, such as a review feed, set a min-height at the most common size. Where you know the number of items, reserve the space for all of them.
Fix 3: stop banners from pushing the page
A cookie notice, promotion bar or "join our newsletter" strip that is inserted above the content after the page has drawn pushes everything down. There are two clean ways to avoid it.
- Overlay instead of insert. Show it as a fixed bar at the bottom, or as a panel over the content. Overlays do not move anything.
- Reserve its space from the first frame. If a bar has to sit at the top, put an empty box of the same height in the page from the start, and fill it when the content is ready. Remove the box only when you also remove the bar.
Take care with the second option for consent banners: many appear only for visitors who have not answered yet, so reserving space for everyone shifts the page for the ones who have. For those, the overlay is the better choice.
Fix 4: make the fallback font match the web font
With font-display: swap, the text first draws in a fallback font and then switches to your web font. If the two fonts have different proportions, every line of text changes size and the page below it moves. Two steps remove most of the shift.
- Reduce the number of fonts and weights the top of the page needs, and preload the one or two files it does use. The same settings help First Contentful Paint.
- Tune a fallback to match. CSS lets you adjust a local font so it takes the same space as the web font:
@font-face {
font-family: "Brand Fallback";
src: local("Arial");
size-adjust: 104%; /* scale the glyphs */
ascent-override: 92%; /* line metrics of the web font */
descent-override: 24%;
line-gap-override: 0%;
}
body { font-family: "Brand", "Brand Fallback", sans-serif; }The four numbers are examples. Each web font needs its own, which you can calculate from the font's metrics or take from a tool. Chrome's article on font fallbacks lists tools and notes that frameworks such as Next.js do this for you. Support differs by browser: Chrome and Firefox handle all four descriptors, while Safari 17 handles only size-adjust, so a smaller shift remains for Safari visitors. For the full method, with the equations and a worked example, see fixing layout shift from web fonts.
Fix 5: keep a place for late content
Reviews, related products, a "recently viewed" strip, a personalized block: anything that arrives after the first paint needs a placeholder of about the right size. A grey skeleton with the final height is enough. If the height varies, reserve the smallest likely height and let the block grow below it. Avoid inserting late content near the top of the screen, because more elements sit below it and all of them move.
For long pages where you use content-visibility: auto to skip drawing sections that are off screen, also set contain-intrinsic-size to a realistic height. Without it, a section that comes into view changes height and shifts everything after it. It works in Chrome 83 and later, Firefox 107 and later and Safari 17 and later.
Fix 6: animate with transforms
Animating top, left, width, height or margins moves the elements around it and counts as layout shift. Moving something with top or left shifts even when the element is alone in its own layer. Use transform: translate() and scale(), and opacity, which do not change the layout. The same goes for headers that shrink as you scroll: change their appearance with a transform or keep the height fixed.
Step 4: check that it worked
- Reload and watch. Throttle the connection in DevTools and look at the first seconds of the load. If something still jumps, you have found the next culprit.
- Run the lab test three times. The CLS and the list of culprits should be shorter.
- Do the post-load checks. Scroll the whole page, open any menus or tabs, and wait for the late banners. Anything that moves is still a problem.
- Check the real-user data in four weeks. The data covers a rolling 28 days.
When a shift is not yours
Some shifts come from outside your page. An ad network that serves different sizes, a consent tool, an embed whose height changes, a browser extension on the tester's machine. Reserve the largest likely space for what you can, tell the vendor about the rest, and accept that a small amount of CLS is normal: the mark is 0.1, not zero.
Check your own page
Run the free audit and look at the layout shift culprits. If an image without dimensions is on the list, fix that first, because it is the cheapest fix. Then load the page on a slow connection and watch for anything that jumps once the page seems to have finished.
Sources
- Optimize Cumulative Layout Shift on web.dev: causes, lab versus field and the animation advice.
- Best practices for fonts: font fallbacks in the Chrome documentation: font metric overrides and
size-adjust. - aspect-ratio and content-visibility on MDN.
- The web-vitals library: the attribution build for CLS.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Core Web Vitals · Book a call