/ CSS Checker Fixes / Layout Shift CSS

How to Fix CSS Layout Shift

Cumulative Layout Shift (CLS) is one of Google's Core Web Vitals. It measures how much your page jumps around as it loads — images appearing and pushing content down, web fonts swapping and reflowing text, ads inserting themselves. CLS above 0.1 hurts ranking eligibility for Page Experience signals. Most CLS problems trace to CSS: missing image dimensions, async-loaded resources without reserved space, and font swaps. The fixes are simple but compound for site-wide gains.

1. Measure current CLS

Step 1
Lab measurement: Lighthouse
DevTools → Lighthouse → Generate report. CLS appears in the Core Web Vitals section. Lab measurements catch obvious shifts but miss user-interaction-triggered ones.
Step 2
Field measurement: Search Console
Search Console → Core Web Vitals report. Real-user 75th-percentile CLS from Chrome User Experience Report. Field data reflects actual user devices, networks, scroll behaviour — lab data doesn't.
Step 3
Visualise shifts in DevTools
DevTools → Performance tab → record a page load. The Experience track shows layout shift events with rectangles highlighting what moved and when.

2. Set explicit image dimensions

The biggest CLS contributor on most sites. Without width and height, images start at zero size and push content down when they load.

<!-- WRONG — zero-size until load -->
<img src="/hero.jpg">

<!-- RIGHT — slot reserved before download -->
<img src="/hero.jpg" width="1200" height="630" alt="...">

Responsive images with aspect-ratio

<img src="/hero.jpg" 
     width="1200" 
     height="630" 
     style="width:100%; height:auto"
     alt="...">

The width/height attributes give the browser the aspect ratio. The CSS lets the image scale responsively. Combined, the slot is reserved at the correct ratio even on smaller screens.

Modern CSS aspect-ratio

.media-card {
  aspect-ratio: 16 / 9;
  background: var(--bg3); /* placeholder colour */
}
.media-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

3. Reserve space for web fonts

⚠️ Web fonts cause shift when they swap from fallback. If the web font's character width differs from the fallback's, text re-flows when the web font arrives.

Use font-display: swap with size-adjust

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
  size-adjust: 107%;       /* match fallback metric */
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

These overrides tell the browser to render the fallback font as if it had the web font's metrics. Result: fallback and web font occupy the same space, swap happens without reflow.

Or use font-display: optional

@font-face {
  /* ... */
  font-display: optional;
}

With optional, if the font doesn't load fast enough, the page sticks with the fallback for the whole session. No swap, no shift. Trade-off: some users never see your web font.

4. Reserve space for ads and embeds

/* Ad slot — reserve 300x250 even before ad loads */
.ad-slot-300x250 {
  display: block;
  width: 300px;
  height: 250px;
  background: var(--bg3); /* placeholder */
}

/* Responsive ad slot with aspect-ratio */
.ad-responsive {
  aspect-ratio: 6 / 5;
  background: var(--bg3);
}

For Google Ads and other dynamic-size ads that vary by viewport, define a min-height matching the smallest expected size and let the ad grow within that container:

.ad-slot {
  min-height: 250px;
  display: flex;
  align-items: center;
  justify-content: center;
}

5. Handle late-inserted content

Cookie banners

Cookie banners that push content down are CLS disasters. Fix: position fixed at the bottom or top, NOT inserted into the document flow.

.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
}

Async-loaded sections

Anything loaded via JS after first paint should occupy reserved space. Use skeleton screens:

.product-grid-loading {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.product-grid-loading::before {
  content: '';
  aspect-ratio: 3 / 4;
  background: var(--bg3);
  border-radius: 8px;
}

Lazy-loaded images

Native lazy loading is fine for CLS as long as the image has width/height:

<img src="/photo.jpg" 
     width="800" height="600"
     loading="lazy" 
     alt="...">

6. Avoid animation-triggered shifts

⚠️ Animating top, left, width, height, margin, padding can cause layout shift on every frame. Use transform and opacity instead — they don't trigger layout.
/* Triggers layout shift */
.element {
  animation: slide 0.3s;
}
@keyframes slide {
  from { left: -100px; }
  to { left: 0; }
}

/* No layout shift */
.element {
  animation: slide 0.3s;
}
@keyframes slide {
  from { transform: translateX(-100px); }
  to { transform: translateX(0); }
}

7. Test the fix

Step 1
Lab re-measure
Run Lighthouse again. CLS should drop significantly. Aim for under 0.1.
Step 2
Wait for field data
Search Console Core Web Vitals updates every 28 days based on real-user data. After 28 days of fixed code, the field CLS should match the lab improvement.
💡 CLS fixes are usually quick wins. A few hours adding width/height to images and font-display fixes can drop site-wide CLS by 50-80%. Few performance interventions have this return on time.

🎨 Re-run the CSS Checker

Verify layout-shift findings are cleared and CLS is under 0.1.

Run CSS Checker →
Related Guides: CSS Checker Fixes  ·  Core Web Vitals Fixes  ·  CLS Debugger Fixes  ·  CSS Checker Guide
💬 Got a problem?