To stop images from shifting your layout, put width and height attributes on every <img> and set height: auto in your CSS. The browser uses those two numbers to reserve the right amount of space before the file arrives, so nothing jumps when it loads. For containers, ads, and embeds that aren't plain images, reserve the space yourself with CSS aspect-ratio or min-height.
What is CLS and what's a good score?
Cumulative Layout Shift is a Core Web Vital that measures visual stability. Each time a visible element moves without the user causing it, the browser records a shift score based on how much of the viewport moved and how far. Shifts that happen close together (less than one second apart, up to five seconds total) are grouped into a "session window." Your CLS is the worst window during the page's life.
Per web.dev, 0.1 or less is good, over 0.25 is poor, and the range between needs improvement. Like the other vitals, it's judged at the 75th percentile of real page loads.
You've felt this. You go to tap a link, an image above it finishes loading, the text drops 300 pixels, and you tap an ad instead. That's the thing CLS punishes.
Why do width and height attributes fix image shifts?
Before the image downloads, the browser doesn't know how tall it'll be. Without hints, it lays the image out at zero height, then pushes everything below it down once the file arrives.
With width and height set, modern browsers apply a default CSS aspect ratio based on those numbers. In effect, the browser's own stylesheet gives the image aspect-ratio: auto 1600 / 900. The auto part means that once the real image loads, its true ratio takes over.
So the browser can compute the height from the width before a single byte of the image arrives. That's the whole trick.
The markup and CSS
<img src="team.jpg"
width="1600" height="900"
alt="Our team at the 2026 offsite">
img {
max-width: 100%;
height: auto;
}
This confuses a lot of people. The attributes don't lock the image at 1600 by 900 pixels. max-width: 100% shrinks it to fit its column, and height: auto keeps the 16:9 ratio as it shrinks. The only thing the numbers need to be is the right ratio. Most people just use the file's real pixel size, which is the easiest way to get it right.
The one thing that breaks this: CSS that sets height to a fixed value while width is fluid. Then the ratio is gone and you get a stretched or squished image.
What about srcset?
If all the files in your srcset share the same ratio, which they should, one pair of width and height covers them all. Use the numbers from any one of them. We explain the rest of that markup in srcset, sizes, and picture.
How do you handle picture with different aspect ratios?
Art direction often means a square crop on phones and a wide crop on desktop. A single width/height pair on the <img> would reserve the wrong space on one of them. The fix is to put dimensions on each <source>:
<picture>
<source media="(max-width: 600px)"
srcset="hero-square.jpg"
width="1000" height="1000">
<img src="hero-wide.jpg"
width="2000" height="800"
alt="Cyclists climbing a mountain pass">
</picture>
This is part of the WHATWG HTML standard, and web.dev reports that Chrome, Firefox, and Safari all support it. Older browsers without support just fall back to the <img> dimensions, so it's safe to add now.
How do you reserve space for things that aren't images?
Use the CSS aspect-ratio property. It's the same mechanism, just set by hand.
.video-embed {
aspect-ratio: 16 / 9;
width: 100%;
}
.video-embed iframe {
width: 100%;
height: 100%;
}
This works well for YouTube and Vimeo embeds, map iframes, and image containers that use object-fit: cover. For a fixed-ratio card thumbnail, set aspect-ratio on the image itself and add object-fit: cover so any odd-ratio uploads get cropped rather than resizing the card.
Ads and third-party embeds
Ads are the classic CLS source, and the hardest. The ad server decides what size to send after the page has loaded. You can't know it in advance, so reserve space for the most likely size:
- Look at your ad reports for the most common size served in each slot.
- Give the slot's container a
min-heightfor that size. Use media queries if mobile and desktop sizes differ. - Don't collapse the slot when no ad fills it. An empty gray box is better than a jump. Show a small "Advertisement" label if the blank space looks odd.
- Avoid placing ad slots near the top of the page where a shift pushes the most content.
The same goes for social embeds, cookie banners, and "subscribe" bars injected at the top. If it appears late, give it a reserved spot or overlay it instead of pushing content down.
Do placeholders and blurry previews help?
They help how the page feels, but they don't fix CLS on their own. A low-quality image placeholder (LQIP), a tiny blurred version shown until the full image loads, only prevents shifts if it takes up the same box as the final image. If the placeholder is 20 pixels tall and the real image is 400, you still get a shift.
So the order is: reserve the space with width/height or aspect-ratio first, then fill it with a solid color, a dominant-color background, or a blurred preview. A simple background-color on the image is often enough and costs nothing. Just keep placeholder images small. A 30 KB "placeholder" defeats the point, and our guide to image compression settings can help keep them tiny.
Do lazy-loaded images need width and height?
Even more than regular ones. An unloaded lazy image with no dimensions is zero pixels tall. When the user scrolls near it, it loads and pushes content down right where they're reading. MDN points out that lazy images without a size can also fail to load at all in some layouts, because a zero-size box never crosses into view. See lazy loading images without hurting LCP for the rest of the lazy-loading rules.
What about web fonts?
Briefly: a web font that swaps in with different metrics than the fallback font can reflow text and cause shifts. Options include font-display: optional, preloading the font, or tuning the fallback with size-adjust. It's a separate topic from images, but it often shows up in the same CLS report, so don't be surprised to see it there.
How do you find what's shifting?
- PageSpeed Insights. The field data at the top tells you whether real users see a CLS problem. The lab diagnostics list the elements with the largest layout shifts.
- DevTools Layout Shift Regions. Open the Command Menu, run "Show Rendering," and check "Layout Shift Regions." Reload the page. Areas that shift flash with a colored overlay. This is the quickest way to spot the culprit by eye.
- DevTools Performance panel. Record a load. The Layout shifts track shows each shift and groups them into clusters. Click one to see which elements moved.
- Scroll and interact. Lab tools mostly measure the initial load. Lazy images, sticky headers, and late ads often shift later. Scroll through the page slowly with Layout Shift Regions on.
Test at a phone width too. Narrow screens make images proportionally taller, so a missing height hurts more there.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
No width/height on <img> | Image starts at zero height, then pushes content down | Add both attributes with the real ratio |
Attributes set, but CSS has height: 300px | Image stretches or squishes on narrow screens | Use height: auto |
| Wrong ratio in the attributes | Space reserved is too tall or short, small shift on load | Match the file's actual dimensions |
One size on <picture> with mixed crops | Wrong box on one breakpoint | Add width/height to each <source> |
| Ad slot collapses when empty | Content jumps when an ad does or doesn't load | Keep a min-height on the slot |
| CMS strips attributes | Markup looks right in the editor, not on the page | Check the rendered HTML, not the template |
That last one bites people more than you'd think. Some page builders and old image-resizing plugins drop the attributes on output. View source on the live page, not the editor preview.
Does this matter for tiny images and icons?
Yes, a little. A 24-pixel icon without dimensions still causes a small shift, and those add up across a nav bar full of them. Inline SVGs set their size from the viewBox and CSS, so give them explicit width and height too. Our guide to SVG images on websites covers that. Browser-tab favicons don't affect layout at all, but if you reuse your icon inside the page header, treat it like any other image. The favicon vs app icon vs logo guide helps sort out which file goes where.
Favicons and touch icons can't shift your layout, since they never render inside the page. They still need correct size hints in the head, though, and the favicon HTML code generator writes those link tags with the right sizes and types.
TL;DR
Put width and height attributes on every image and use height: auto in CSS, so the browser reserves the right box before the image loads. Add dimensions to each <source> when your crops differ, and use aspect-ratio or min-height for embeds and ads. Aim for a CLS of 0.1 or less, and use Layout Shift Regions in DevTools to catch what's moving.
