/ โ† CLS Debugger Fixes / CLS in Responsive Layouts
In this guide
  1. What makes a responsive shift different
  2. When srcset is safe and when it is not
  3. Finding the width that breaks
  4. The fix, step by step
  5. JavaScript that rearranges the page
  6. Viewport units, toolbars and scrollbars
  7. Keeping responsive layouts stable
  8. Questions and answers

What makes a responsive shift different

An image without any size information shifts at every width, which is covered in fixing CLS from images without dimensions. A responsive shift is more slippery: the page reserves a box that is right at one width and wrong at another. Because most people test on one desktop screen and perhaps one phone, the broken width slips through.

There are four common patterns:

What they have in common is that the box the browser drew first is not the box the page ends up with, and the difference depends on the screen.

When srcset is safe and when it is not

A plain srcset with a sizes attribute offers the browser the same picture at different resolutions. Every candidate has the same proportions, so the width and height attributes on the <img> are correct whichever file is picked. This kind of responsive image does not cause shifting by itself.

<img src="/img/walk-800.jpg"
     srcset="/img/walk-400.jpg 400w, /img/walk-800.jpg 800w, /img/walk-1600.jpg 1600w"
     sizes="(min-width: 60em) 50vw, 100vw"
     width="1600" height="1000"
     alt="Coastal footpath above the bay">

Trouble starts when the candidates are different crops. That is art direction, and it belongs in a <picture> element with a separate <source> per breakpoint. If a developer has put differently shaped files into a single srcset, the attributes can only be right for one of them. Move the crops into <source> elements, or re-crop all candidates to one shape.

Dimensions on each source

Current browsers accept width and height attributes on each <source> inside <picture>, and use the matching source's numbers to reserve the box. That is the cleanest fix for art direction:

<picture>
  <source media="(min-width: 64em)"
          srcset="/img/sale-wide-2100.jpg"
          width="2100" height="900">
  <source media="(min-width: 40em)"
          srcset="/img/sale-mid-1200.jpg"
          width="1200" height="800">
  <img src="/img/sale-tall-800.jpg"
       width="800" height="1000"
       alt="Autumn sale at Linden Street Interiors">
</picture>

The fallback <img> carries the dimensions for the narrowest crop, because it is used when no media condition matches. Keep img { height: auto; } in your CSS so the ratio controls the height.

A CSS backstop

If your templating makes per-source attributes awkward, or you need to support an older browser, set the ratio in CSS at the same breakpoints the sources use:

.sale-banner img { width: 100%; height: auto; aspect-ratio: 4 / 5; }
@media (min-width: 40em) { .sale-banner img { aspect-ratio: 3 / 2; } }
@media (min-width: 64em) { .sale-banner img { aspect-ratio: 7 / 3; } }
โš ๏ธ The media queries in the CSS must use exactly the same widths as the media attributes on the sources. If the stylesheet switches at 40em and the markup at 42em, there is a band of screen sizes where the box and the image disagree, and that band is where your shift lives.

Finding the width that breaks

Start with real-user data, because it tells you which device class is affected. The Core Web Vitals report in Search Console splits results into mobile and desktop, and page groups there often point at a template. If only mobile is poor, test phones first; if only desktop, think about scrollbars, wide art-directed images and sidebars.

  1. Open the page in Chrome DevTools with the device toolbar turned on, set the width to a small phone, and enable slow network throttling with the cache disabled.
  2. Record a reload in the Performance panel. In the layout shift track, select each shift and check which elements moved.
  3. Repeat at each of your CSS breakpoints and a few pixels either side. Shifts that only occur in a narrow band usually mean markup and CSS use different breakpoint values.
  4. Run the CLS Debugger and compare the elements it names with what you saw at each width.

To see which art-directed file a picture element chose, and whether the reserved box matched it, paste this into the console after load:

document.querySelectorAll('picture img').forEach((img) => {
  const box = (img.clientWidth / img.clientHeight).toFixed(3);
  const file = (img.naturalWidth / img.naturalHeight).toFixed(3);
  console.log(img.currentSrc, 'box', box, 'file', file,
    box === file ? 'ok' : 'MISMATCH');
});

A mismatch after load usually means object-fit is cropping, which is fine; a mismatch you can see as a jump during load means the reserved ratio was wrong.

The fix, step by step

Step 1

Find the screen widths where the shift happens

Check whether Search Console reports poor CLS for mobile, desktop or both, then reproduce the load in the DevTools device toolbar with slow throttling and the cache disabled. Record a reload at each breakpoint in your stylesheet and a few pixels either side of it. Note the widths where content moves and which element above it changed shape. The CLS Debugger's list of moving elements helps you confirm what you see.

Step 2

Give each art-directed source its own dimensions

For every <picture> element whose crops have different proportions, add width and height attributes to each <source> using that crop's real pixel size, and put the narrowest crop's dimensions on the fallback <img>. If differently shaped files have been mixed into one srcset, split them into separate sources with media conditions, or re-export every candidate so that all of them share the same shape.

Step 3

Match the reserved ratio to each breakpoint in CSS

Where attributes are not practical, or where a wrapper rather than the image sets the height, set aspect-ratio in CSS inside media queries that use exactly the same widths as the source media attributes. Keep height: auto on the image. Search for rules that set a fixed height at one breakpoint only, because the browser applies them at some widths and falls back to content height at others.

Step 4

Move layout decisions from JavaScript into CSS

Look for scripts that read window.innerWidth, call matchMedia or measure elements, then change classes, heights or which component is rendered. Replace them with media queries or container queries wherever possible. When a script must choose, such as a server-rendered app, render both versions and hide one with CSS, or render the mobile version first so the first paint already matches most visitors.

Step 5

Deliver breakpoint styles with the first paint

If you inline critical CSS and load the rest later, make sure the inlined part includes the small-screen rules for everything above the fold, not only the desktop layout. Check stylesheets loaded with a media swap trick or added by JavaScript, since any rule in them that changes layout will reflow the page when it applies. Load layout rules normally and defer only decoration.

Step 6

Use stable viewport units and scrollbar space

Replace hero heights in dvh with svh, or with vh plus a min-height, so the section does not resize as the mobile address bar collapses and returns. On desktop layouts, add scrollbar-gutter: stable to the root element so the page width does not change when content grows tall enough to need a scrollbar and the classic scrollbar appears. Both changes are one line of CSS each.

Step 7

Test every breakpoint on a slow connection

Clear page and CDN caches, then repeat the throttled recordings at each breakpoint and either side of it, in portrait and landscape on a phone size. Re-run the CLS Debugger on the templates you changed. Look at the mobile and desktop Core Web Vitals figures separately over the following weeks, because an improvement on one device class can hide a problem that remains on the other.

JavaScript that rearranges the page

A large share of responsive shifts are not about images at all. They come from code that decides the layout after the first paint.

Mobile and desktop components chosen in script

In server-rendered apps, the server cannot know the screen width, so it renders a default, often the desktop navigation. When the page hydrates in the browser, a hook reads the width and swaps in the mobile menu. The header changes height and the page moves. The robust answer is to send markup that works at every width and let CSS decide what is visible:

<nav class="nav-desktop">โ€ฆ</nav>
<nav class="nav-mobile">โ€ฆ</nav>

.nav-mobile { display: block; }
.nav-desktop { display: none; }
@media (min-width: 60em) {
  .nav-mobile { display: none; }
  .nav-desktop { display: flex; }
}

Framework-specific patterns for this, including hydration mismatches, are in CLS in React and Next.js.

Sliders and carousels that measure themselves

Many carousel scripts show all slides stacked on top of each other until they initialise, then measure the first slide and set the track's height. On a phone, where a slide might be twice as tall as on desktop, the jump is large. Give the carousel container an aspect-ratio or min-height per breakpoint, and hide slides after the first with CSS until the script is ready, so the uninitialised and initialised versions take the same space.

Equal-height scripts

Older themes use JavaScript to make cards in a row the same height. On narrow screens, where cards stack in one column, the script may still run and change heights after load. CSS grid and flexbox give equal heights without any script; remove the old code.

๐Ÿ’ก Container queries let a component respond to the width of its own column rather than the whole screen, entirely in CSS. They are a good replacement for scripts that measure a parent element and switch classes.

Viewport units, toolbars and scrollbars

Unit or settingHow it behavesStability
vhBased on the large viewport on most mobile browsers, so it stays the same as the toolbar moves.Stable, but content can sit under the toolbar at first.
svhThe small viewport, with toolbars fully shown. Never changes as they collapse.Stable and fully visible. A good default for heroes.
lvhThe large viewport, with toolbars hidden.Stable.
dvhThe dynamic viewport. Updates as the toolbar appears and disappears.Changes during scrolling, moving anything below it.
scrollbar-gutter: stableReserves room for a classic scrollbar even when the page does not yet scroll.Stops the page narrowing when a scrollbar appears on desktop.

The scrollbar issue is easy to miss. On Windows and some Linux desktops, scrollbars take up real width. A short page renders without one, then a late block of content makes the page tall enough to scroll, the scrollbar appears, the page narrows by around 15 pixels, text rewraps and blocks change height. Reserving the gutter from the start removes that reflow. Overlay scrollbars on macOS and phones take no width, so you will not see this on those devices.

Keeping responsive layouts stable

For the scoring rules behind all this, and how to rank shifts by impact, see the CLS Guide.

Questions and answers

1. Why is my CLS poor on mobile but fine on desktop?

Phones are where the reserved box is most often wrong. Art-directed images frequently use a taller crop on small screens while the markup only describes the desktop image, mobile menus are often swapped in by script after load, critical CSS is sometimes written for the desktop layout only, and hero sections in viewport units react to the moving address bar. Mobile connections are also slower, which leaves more time between the first paint and the moment a late file or script changes the layout.

2. Does using srcset cause layout shift?

Not if every file in the srcset has the same proportions. In that case the browser is only choosing a sharper or smaller version of the same picture, and the width and height attributes on the img tag describe all of them correctly. Shifts happen when the files are different crops with different shapes. That is art direction, and it should use a picture element with a source for each breakpoint, each carrying its own width and height attributes.

3. Can I put width and height attributes on a source element?

Yes. Current versions of the major browsers accept width and height on source elements inside a picture element and use the dimensions of whichever source matches to reserve space. Put the narrowest crop's dimensions on the fallback img tag, since it is used when no media condition matches. If you need a belt-and-braces approach, also set aspect-ratio in CSS at the same breakpoints, making sure the media queries match the source media attributes exactly.

4. Does rotating a phone count as layout shift?

Rotating a phone changes the viewport, and the whole page is laid out again for the new width. That is a response to the device changing rather than content arriving late, and it is not the kind of problem this guide is about. What matters is that after rotation, images and components settle at their new size straight away instead of loading or measuring themselves again and jumping a second time, so test landscape loads as well as portrait ones.

5. Should I stop using dvh units?

Not everywhere, but avoid them for anything that other content sits below. The dvh unit updates as the mobile browser toolbar appears and disappears, so an element sized with it changes height while the visitor scrolls and moves everything after it. For a full-screen hero, svh gives a height that always fits with the toolbar shown and never changes. Use dvh for things like fixed overlays and full-screen menus, where nothing else is pushed around.

6. Why does my desktop layout jump by a few pixels sideways?

The most likely cause is a classic scrollbar appearing. On Windows and some Linux desktops, the scrollbar takes up real width. If the page is short when first drawn and becomes scrollable once more content loads, the scrollbar appears, the page narrows, and text rewraps. Adding scrollbar-gutter stable to the root element reserves the space from the start. You will not see this on a Mac or phone, which is why it is often missed during testing.

7. Is it better to use JavaScript or CSS for responsive layout switches?

CSS, wherever you can. Media queries and container queries apply before the first paint, so the page is drawn in its final shape. JavaScript runs later, after the browser has already laid out and often painted a default layout, so any change it makes can move content. Keep scripts for behaviour, such as opening a menu when tapped, and let CSS decide which layout, which component and which size each screen gets.

See which elements move on your page

The CLS Debugger records every layout shift while your page loads, names the elements involved, calculates the cumulative score and ranks each shift by its impact, so you can compare what moves at each screen size.

Open CLS Debugger Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All CLS Debugger Fixes  ·  CLS from Images Without Dimensions  ·  CLS in React and Next.js  ·  CLS Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.