How to Fix Cumulative Layout Shift (CLS): 2026 CSS Guide

Learn how to fix Cumulative Layout Shift (CLS) with modern CSS. Fix un-dimensioned images, font FOIT/FOUT, and dynamic ads with verified code and before-after data.

BugViso

16 min read

Few technical failures erode user trust faster than an unexpected layout jump. A visitor taps an interactive confirmation button, only for an unsized hero image or an asynchronous ad banner to pop into the DOM milliseconds prior, shifting the button downward and triggering an accidental click on a paid subscription or unwanted link.

Layout instability is not merely an annoyance; it directly destroys conversion rates, inflates bounce rates, and damages your organic search visibility under Google's Page Experience ranking signals. To fix Cumulative Layout Shift across modern web applications, frontend engineers and technical SEOs must understand the browser rendering pipeline, reserve layout space defensively, and implement modern CSS layout primitives.

In this comprehensive guide, you will master the underlying mathematics behind layout shift calculations, diagnose the five primary causes of visual instability, apply concrete CSS and HTML fixes with before-and-after performance metrics, and automate shift detection across your entire site.


Understanding Cumulative Layout Shift: The Math and Metrics Behind Visual Instability

Cumulative Layout Shift (CLS) is one of Google's three Core Web Vitals metrics, measuring the visual stability of a web page throughout its entire lifecycle. Unlike load-time metrics such as Largest Contentful Paint (LCP), CLS does not measure elapsed time in milliseconds. Instead, it is a unitless mathematical score representing the frequency and magnitude of unexpected layout shifts.

Diagram
+-------------------------------------------------------------------------+

|                  CORE WEB VITALS: CLS SCORING THRESHOLDS                |

+-----------------------------+-----------------------------+-------------+

|  GOOD (Passing)             |  NEEDS IMPROVEMENT          |  POOR       |
|  Score <= 0.10              |  0.10 < Score <= 0.25       |  Score > 0.25|
|  (Green)                    |  (Amber)                    |  (Red)      |

+-----------------------------+-----------------------------+-------------+

|  Evaluated at the 75th percentile of all mobile and desktop page loads. |

+-------------------------------------------------------------------------+

To achieve a "Good" rating, your page must maintain a CLS score of 0.10 or lower at the 75th percentile of real-world user page visits. For an overview of how CLS fits alongside LCP and INP, read our Core Web Vitals explained pillar guide.

How the Browser Calculates Layout Shift Scores

The browser calculates layout instability using the Layout Instability API specification. Whenever an unstable DOM element changes its start position between render frames without preceding user input, the browser generates a LayoutShift entry.

The individual layout shift score is computed using two factors:

$$\text{Layout Shift Score} = \text{Impact Fraction} \times \text{Distance Fraction}$$

Diagram
+-------------------------------------------------------------------------+

|                    THE LAYOUT SHIFT MATHEMATICAL FORMULA                |
|                                                                         |
|  1. IMPACT FRACTION:                                                    |
|     Total visual area of the viewport occupied by the shifting element  |
|     in both its starting frame and its ending frame combined.           |
|                                                                         |
|  2. DISTANCE FRACTION:                                                  |
|     The maximum vertical or horizontal distance the element moved,      |
|     divided by the largest viewport dimension (height or width).        |
|                                                                         |
|  EXAMPLE CALCULATION:                                                   |
|  - Viewport Height: 1000px                                              |
|  - Element occupies 500px height (50% of viewport = 0.50).              |
|  - Element drops down by 200px (200px / 1000px = 0.20 distance).        |
|  - Combined Impact Area: 500px + 200px = 700px (0.70 impact fraction).  |
|  - Individual Shift Score: 0.70 * 0.20 = 0.140 (Fails the 0.10 target)  |

+-------------------------------------------------------------------------+

The Windowing Model: Maximum Session Windows

In earlier iterations of Core Web Vitals, CLS accumulated indefinitely across the entire lifetime of a long-lived Single Page Application (SPA), artificially penalizing pages with long user engagement times.

According to the official web.dev CLS specification, Chrome aggregates layout shifts into Session Windows:

  • A session window begins when the first layout shift occurs.
  • The window continues accumulating shift scores as long as consecutive shifts happen within 1 second of each other.
  • A window reaches its hard cap after a maximum duration of 5 seconds.
  • The overall CLS reported for the page is the maximum session window score recorded during the entire session.

The hadRecentInput Exception

Not all layout shifts are penalized. If a DOM shift occurs within 500 milliseconds of a discrete user interaction (such as a tap, click, or keypress), the hadRecentInput flag is set to true.

The browser excludes user-initiated shifts from the CLS calculation because the visual change is an expected response to user intent (for example, expanding an accordion or toggling a dropdown menu). However, asynchronous network callbacks after a click (such as an API response resolving 800ms later and pushing content down) will exceed the 500ms grace window and trigger a heavily penalized shift.


Diagnostic Breakdown: The 5 Culprits Behind Layout Shifts

To systematically reduce visual instability, we must analyze the specific browser rendering mechanics responsible for unplanned geometry changes.

Root CauseBrowser Failure Mechanism
Un-dimensioned Media AssetsReflow triggered upon header parsing
Web Font FOUT / FOITFallback vs web font metric mismatch
Injected Ads & Dynamic SlotsZero-height DOM nodes injected async
Non-Composited CSS MovesGeometrical top/left/margin reflow
Hydration State JumpsClient-side markup replacing SSR DOM

Root Cause 1: Images, Videos, and Iframes Without Explicit Dimensions

When an HTML parser encounters an <img>, <video>, or <iframe> tag lacking explicit layout dimensions, it assigns the element an initial rendered height of 0px.

As the browser streams the image binary over the network and parses the image header, it discovers the natural intrinsic dimensions (e.g., $1920 \times 1080$). The browser's layout engine must then trigger an immediate reflow, resizing the element from 0px to its computed height and shifting every subsequent DOM node down the page.

Diagram
BEFORE (Shift Occurs):
+-------------------------+

| [Paragraph Text]        |

| [Unsized <img> (0px)]   | <-- Initial paint: image occupies 0px height

| [Call to Action Button] |

+-------------------------+

          |

          | (Image binary loads 400ms later)
          v
+-------------------------+

| [Paragraph Text]        |
| +---------------------+ |

| | Rendered Image      | | <-- Image expands to 350px height

| | (350px height)      | |
| +---------------------+ |

| [Call to Action Button] | <-- Jumps 350px downward (Massive CLS)
+-------------------------+

The Modern CSS Fix: HTML Aspect Ratio Attributes + Fluid CSS

Modern evergreen browsers compute an element's intrinsic aspect ratio directly from HTML width and height attributes before the external file has completed downloading.

Bad Practice: Missing Dimensions

html
<!-- BAD: Browser has 0 information about aspect ratio prior to download -->
<img src="/assets/hero-architecture.webp" alt="Cloud Architecture Overview" class="responsive-banner" />

<style>
.responsive-banner {
  width: 100%;
  height: auto; /* Height remains 0px until image metadata is decoded */
}
</style>

Best Practice: Explicit Attributes with Responsive CSS

Always supply raw pixel integer dimensions in HTML, paired with modern responsive CSS.

html
<!-- GOOD: Raw pixel dimensions provide default aspect ratio -->
<img 
  src="/assets/hero-architecture.webp" 
  width="1200" 
  height="675" 
  alt="Cloud Architecture Overview" 
  class="responsive-banner" 
  loading="eager" 
  fetchpriority="high"
/>

<style>
.responsive-banner {
  width: 100%;
  height: auto;
  /* Modern CSS ensures aspect-ratio calculation from HTML attributes */
  aspect-ratio: 1200 / 675;
  display: block;
}
</style>

Handling Modern Responsive Images (<picture> & srcset)

When serving varied aspect ratios across responsive breakpoints (e.g., square $1:1$ on mobile viewports and landscape $16:9$ on desktop viewports), use the CSS aspect-ratio property inside scoped media queries.

html
<picture class="hero-picture-slot">
  <source 
    media="(min-width: 1024px)" 
    srcset="/images/hero-desktop.webp" 
    width="1600" 
    height="900"
  />
  <source 
    media="(max-width: 1023px)" 
    srcset="/images/hero-mobile.webp" 
    width="800" 
    height="800"
  />
  <img 
    src="/images/hero-mobile.webp" 
    width="800" 
    height="800" 
    alt="Platform Performance Dashboard" 
    class="hero-fluid-img"
  />
</picture>

<style>
.hero-picture-slot {
  display: block;
  width: 100%;
}

.hero-fluid-img {
  width: 100%;
  height: auto;
  display: block;
}

/* Ensure mobile layout allocates 1:1 box before load */
@media (max-width: 1023px) {
  .hero-fluid-img {
    aspect-ratio: 1 / 1;
  }
}

/* Ensure desktop layout allocates 16:9 box before load */
@media (min-width: 1024px) {
  .hero-fluid-img {
    aspect-ratio: 16 / 9;
  }
}
</style>

Measured Real-World Impact

Diagram
+-------------------------------------------------------------------------+

|                  PERFORMANCE AUDIT: HERO IMAGE OPTIMIZATION             |

+--------------------------+-----------------------+----------------------+

| Configuration            | CLS Metric Score      | Assessment           |

+--------------------------+-----------------------+----------------------+

| Missing Dimensions       | 0.284                 | Poor (Fails Audit)   |
| Width/Height + Aspect-Ratio| 0.000               | Good (Zero Shift)    |

+--------------------------+-----------------------+----------------------+

By reserving layout space in advance, hero image rendering no longer displaces content below the fold. For additional asset delivery improvements that also accelerate load speeds, consult our guide on how to improve Largest Contentful Paint (LCP).


Root Cause 2: Web Fonts, FOUT, and FOIT Shifts

Web fonts introduce layout shifts when the custom font file downloads after the initial page paint, causing text to re-render in a new typeface with different geometry.

Diagram
+-------------------------------------------------------------------------+

|                         FONT RENDERING SHIFT MODES                      |
|                                                                         |
|  1. FOUT (Flash of Unstyled Text):                                      |
|     Browser paints fallback system font (e.g., Arial). When custom font |
|     (e.g., Inter) downloads, text re-renders. If character widths or    |
|     line heights differ, paragraphs wrap into new lines, shifting all    |
|     content below.                                                      |
|                                                                         |
|  2. FOIT (Flash of Invisible Text):                                     |
|     Browser hides text while waiting for custom font. When downloaded,  |
|     text suddenly appears, expanding container heights from 0px to full |
|     content height.                                                     |

+-------------------------------------------------------------------------+

The CSS Solution: font-display and Font Metric Overrides

To eliminate font shifts, configure the CSS font-display descriptor and synchronize the vertical and horizontal geometry of your system fallback font using CSS metric override descriptors (size-adjust, ascent-override, descent-override, and line-gap-override).

Strategy A: font-display: optional (Zero-Shift Guarantee)

For secondary body text and non-branded UI elements, font-display: optional grants the custom font an extremely short block window (100ms). If the font is already cached in memory, it renders immediately; if not, the browser renders the fallback font permanently for that page session and caches the web font for subsequent navigations.

css
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: optional; /* Eliminates layout shifts on slow connections */
  src: url('/fonts/inter-variable.woff2') format('woff2');
}

Strategy B: Metric Matching with size-adjust (For font-display: swap)

If your branding strictly mandates that custom web fonts swap into view even on slower connections, match the bounding box of your fallback system font to your web font's exact dimensions.

css
/* 1. Declare the Primary Web Font */
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/outfit-v11-latin-600.woff2') format('woff2');
}

/* 2. Create a Matched Fallback Font */
@font-face {
  font-family: 'Outfit-Fallback';
  src: local('Arial');
  /* Scale Arial's glyph widths and bounding box to match Outfit */
  size-adjust: 104.5%;
  ascent-override: 98%;
  descent-override: 26%;
  line-gap-override: 0%;
}

/* 3. Apply the Font Stack */
body {
  font-family: 'Outfit', 'Outfit-Fallback', sans-serif;
  line-height: 1.5;
}

Preloading Critical Above-the-Fold Fonts

Preload your primary typography file in the document <head> to ensure font binary arrival before initial render execution:

html
<link 
  rel="preload" 
  href="/fonts/outfit-v11-latin-600.woff2" 
  as="font" 
  type="font/woff2" 
  crossorigin="anonymous"
/>

Measured Real-World Impact

Diagram
+-------------------------------------------------------------------------+

|                  PERFORMANCE AUDIT: WEB FONT STABILIZATION              |

+--------------------------+-----------------------+----------------------+

| Configuration            | CLS Metric Score      | Assessment           |

+--------------------------+-----------------------+----------------------+

| Unmanaged Web Font Swap  | 0.162                 | Needs Improvement    |
| Metric-Matched Fallback  | 0.003                 | Good (Passing)       |

+--------------------------+-----------------------+----------------------+

Ad servers, marketing widgets, cookie consent bars, and dynamic recommendation engines frequently insert elements into the DOM asynchronously without pre-allocated screen real estate.

When a 250px-tall banner ad initializes halfway through page reading, it pushes every paragraph and interactive element down by 250px, generating an instant shift score penalty above 0.20.

Diagram
+-------------------------------------------------------------------------+

|                     DYNAMIC CONTENT INJECTION COLLISION                 |
|                                                                         |
|  [Header Navigation]                                                    |
|  +-------------------------------------------------------------------+  |
|  | DYNAMIC AD SLOT (Initially rendered at 0px x 0px)                |  |
|  +-------------------------------------------------------------------+  |
|  | Content Title: "Infrastructure Optimization Guide"                |  |
|  | Paragraph text reading in progress...                             |  |
|                                                                         |
|  (Ad script loads 1200ms later -> Container expands to 250px height)    |
|                                                                         |
|  [Header Navigation]                                                    |
|  +-------------------------------------------------------------------+  |
|  | +---------------------------------------------------------------+ |  |
|  | | [SPONSORED LEADERBOARD BANNER - 970x250]                      | |  |
|  | +---------------------------------------------------------------+ |  |
|  +-------------------------------------------------------------------+  |
|  | Content Title (DISPLACED DOWNWARD BY 250PX)                       |  |
|  | Paragraph text (READER LOSES READING POSITION)                    |  |

+-------------------------------------------------------------------------+

The CSS Solution: Slot Reservation and Container Skeletons

Never permit dynamic components or third-party ad scripts to inject directly into unbounded containers. Always construct a reserved structural slot using min-height, contain, or CSS Grid boundaries.

Implementing a Zero-Shift Ad Slot

html
<!-- Ad Wrapper with Reserved Bounding Box -->
<div class="ad-slot-container leaderboard-slot">
  <div class="ad-placeholder-label">Advertisement</div>
  <div id="dfp-leaderboard-unit" class="ad-target-node">
    <!-- Ad Script Injects Here -->
  </div>
</div>

<style>
.ad-slot-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  margin: 1.5rem auto;
  overflow: hidden;
}

/* Reserve exact dimensions for known standard display units */
.leaderboard-slot {
  width: 100%;
  max-width: 970px;
  /* Reserve minimum height corresponding to the expected asset format */
  min-height: 250px;
  /* Prevent child content recalculations from triggering ancestor reflows */
  contain: layout style;
}

.ad-placeholder-label {
  font-size: 0.75rem;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.25rem;
}

@media (max-width: 768px) {
  .leaderboard-slot {
    min-height: 100px;
    max-width: 320px;
  }
}
</style>

Handling Unfilled Ad Units Gracefully

If an ad provider fails to return a creative (an "unfilled impression"), avoid immediately collapsing the container to height: 0px, as collapsing an empty container creates a layout shift just as severe as expanding one.

Instead, preserve the container dimensions and render a fallback branded promotional card or self-hosted content recommendation.

javascript
// DFP / Google Publisher Tag Event Listener Example
googletag.cmd.push(function() {
  googletag.pubads().addEventListener('slotRenderEnded', function(event) {
    if (event.isEmpty) {
      const slotContainer = document.getElementById(event.getSlot().getSlotElementId()).parentElement;
      // DO NOT do: slotContainer.style.display = 'none'; (Triggers CLS)
      // INSTEAD: Render alternative internal content without size alteration
      slotContainer.classList.add('render-house-creative');
    }
  });
});

Never inject top-affixed alert bars that push <body> or <main> content down after page load. Instead:

  1. Use position: fixed or position: sticky anchored to the viewport bottom.
  2. Alternatively, render top alert banners directly inside the server-rendered HTML with full dimensions established upfront.

We benchmarked eight banner patterns side by side. A push-down top bar alone scored 0.13 CLS on mobile, while fixed and transform-animated banners scored zero. The numbers and an A/B measurement script are in our guide to cookie banner CLS.

css
/* Non-shifting Bottom Consent Notification */
.gdpr-consent-drawer {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 3rem);
  max-width: 640px;
  z-index: 9999;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  /* Fixed positioning removes the element from standard document flow */
}

Root Cause 4: Animations and CSS Transitions Triggering Reflow

Animating CSS geometric properties forces the browser to recalculate document geometry on every single animation tick. When element coordinates shift inside normal document flow, the browser flags those movements as layout instability.

Diagram
+-------------------------------------------------------------------------+

|                  CSS PROPERTIES: REFLOW VS COMPOSITOR                   |

+-----------------------------+-------------------------------------------+

| TRIGGERS REFLOW (High CLS)  | COMPOSITOR-ONLY (Zero CLS Impact)         |

+-----------------------------+-------------------------------------------+

| top / bottom / left / right | transform: translate(x, y)                |
| width / height / max-height | transform: scale(x, y)                    |
| margin / padding            | transform: rotate(deg)                    |
| border-width                | opacity                                   |

+-----------------------------+-------------------------------------------+

Bad Practice: Animating Geometry Properties

css
/* BAD: Animating 'top' and 'height' forces continuous layout reflow */
.collapsible-sidebar {
  position: absolute;
  top: -300px;
  height: 300px;
  transition: top 0.3s ease-in-out;
}

.collapsible-sidebar.is-open {
  top: 0px; /* Layout Shift triggered during entire transition duration */
}

Best Practice: Hardware-Accelerated Compositor Transforms

CSS properties transformed via transform and opacity operate on dedicated compositor layers on the GPU. They alter visual presentation without modifying the geometric bounding box in document flow.

css
/* GOOD: Transform-based animation runs entirely on the compositor thread */
.collapsible-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 320px;
  height: 100vh;
  transform: translateX(-100%);
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
  z-index: 100;
}

.collapsible-sidebar.is-open {
  transform: translateX(0); /* Zero CLS score impact */
}

Root Cause 5: Client-Side Rendering, Hydration Mismatches, and Single-Page App Shifts

In modern JavaScript architectures (such as Next.js, Nuxt, and Remix), client-side hydration frequently introduces visual shifts. When the server renders a lightweight skeleton or partial state, and client-side JavaScript attaches 1.5 seconds later with full personalization or data-driven heights, elements jump violently.

Diagram
SSR Render (Hydration Step 1):
+---------------------------------------------+

| Header                                      |
| [Generic Skeleton Bar: 20px height]         |
| Body Content Paragraphs                     |

+---------------------------------------------+

              |

              | (Client-side JS hydrates user profile data)
              v
Hydrated DOM (Hydration Step 2):
+---------------------------------------------+

| Header                                      |
| +-----------------------------------------+ |

| | User Alert Box: Multi-line Notification | | <-- Expands to 110px

| | Action Required on Billing Details      | |
| +-----------------------------------------+ |

| Body Content Paragraphs (DISPLACED 90PX)    | <-- Penalized Layout Shift
+---------------------------------------------+

The Solution: CSS contain-intrinsic-size with content-visibility

For complex dynamic data feeds and virtualized lists, leverage modern CSS content-visibility: auto paired with explicit contain-intrinsic-size estimation. This allows the browser to bypass layout calculations for off-screen components while maintaining exact placeholder scroll heights.

css
/* Off-screen feed cards rendered efficiently without scroll jumping */
.dynamic-card-item {
  content-visibility: auto;
  /* Tell browser the estimated rendered dimensions before layout execution */
  contain-intrinsic-size: auto 380px;
  margin-bottom: 1.5rem;
}

Managing Conditional React Components Defensively

Ensure conditional UI elements (such as authentication bars, alerts, or personalized discounts) do not cause sudden reflows. Reserve their exact bounding boxes in the server-rendered HTML payload.

tsx
// React / Next.js Component with Reserved Space
export function UserProfileBadge({ user, isLoading }: { user: UserData | null; isLoading: boolean }) {
  return (
    <div className="user-badge-slot">
      {isLoading ? (
        <div className="skeleton-pill" aria-hidden="true" />
      ) : user ? (
        <div className="user-info-pill">
          <img 
            src={user.avatarUrl} 
            width={32} 
            height={32} 
            alt={user.name} 
            className="avatar" 
          />
          <span>{user.name}</span>
        </div>
      ) : null}
    </div>
  );
}

// Accompanying CSS ensuring uniform slot dimensioning
// .user-badge-slot { min-height: 40px; min-width: 140px; display: flex; align-items: center; }

For applications suffering from interaction lag alongside visual shifts, examine our in-depth guide on Interaction to Next Paint (INP) explained.


How BugViso Catches CLS and Layout Drift Automatically

Diagnosing layout shifts manually in browser developer tools across hundreds of responsive URLs is inefficient and error-prone. Modern engineering teams require automated continuous quality assurance across their complete crawl graph.

Diagram
+-------------------------------------------------------------------------+

|                  BUGVISO AUTOMATED LAYOUT AUDIT PIPELINE                |
|                                                                         |
|  [Target URL Submitted]                                                 |
|            |                                                            |
|            v                                                            |
|  [Headless Chromium + Multi-Page Crawl Engine]                          |
|            |                                                            |
|            +---> 1. Advanced SEO Intelligence Engine                    |
|            |        (Audits un-dimensioned <img>, missing width/height) |
|            |                                                            |
|            +---> 2. Core Web Vitals & Simulation Engine                 |
|            |        (Captures LayoutShift entries via web-vitals IIFE)  |
|            |        (Simulates Mobile Pixel 5 & 3G network throttling)  |
|            |                                                            |
|            +---> 3. Visual & Layout Quality Assurance Engine            |
|            |        (OpenCV SSIM full-page diff + text clipping checks) |
|            |                                                            |
|            v                                                            |
|  [Prioritized Remediation Playbook + Branded PDF Executive Report]      |

+-------------------------------------------------------------------------+

When you run an automated website scan with BugViso, the background worker executes a multi-stage audit pipeline specifically designed to surface layout instability:

  1. Advanced SEO Intelligence Engine (Asset Dimension Inspection): The crawler inspects the live rendered DOM of every discovered page, identifying all <img>, <picture>, and <iframe> elements missing explicit width and height attributes or aspect-ratio declarations.
  2. Core Web Vitals Engine (Dual Desktop & Mobile Emulation): Utilizing the official self-hosted web-vitals library alongside native PerformanceObserver listeners, BugViso measures precise CLS scores on both desktop viewports and simulated mobile devices (emulating a Google Pixel 5 over throttled network profiles).
  3. Visual Quality Assurance & OpenCV Structural Similarity (SSIM): BugViso captures full-page visual baselines, scanning parent-child layout containers to identify clipped text, overlapping elements, and structural layout drift.
  4. Actionable Remediation Playbook: Rather than presenting an unformatted raw metrics dump, BugViso pairs each detected layout violation with prioritized, developer-facing fix actions and exact DOM selectors directly within the interactive dashboard and downloadable executive PDF report.

For the full list of what BugViso tests here, see the LCP, INP and CLS speed test.


Common Mistakes When Trying to Fix Cumulative Layout Shift

Even experienced engineering teams encounter pitfalls when optimizing for visual stability. Avoid these common implementation mistakes:

Common MistakeConsequence
Global height: auto !important Overrides aspect-ratio calculations
CSS visibility: hidden Still occupies space, shifts upon resize
Testing Only Initial Load Misses scroll-triggered layout shifts
Over-Reserving Slot Height Creates excessive blank space on mobile

1. Overriding HTML Width/Height with Heavy CSS Resets

A standard legacy CSS reset often contains:

css
/* DANGEROUS: Destroys automatic aspect ratio calculation in some browsers */
img {
  max-width: 100%;
  height: auto !important;
}

When !important forces immediate height overrides before CSS layout calculations finish, it can conflict with inline aspect ratios. Ensure modern rules omit !important and explicitly declare aspect-ratio: attr(width) / attr(height) or declare dedicated container sizes.

2. Relying on visibility: hidden for Dynamic Loaders

Hiding an unpopulated widget with visibility: hidden does not remove it from the rendering flow. If that widget changes height once populated with dynamic data, it will trigger an identical layout shift regardless of initial visibility flags.

3. Auditing Only Initial Page Load (Ignoring Mid-Session Shifts)

CLS is calculated across the full lifespan of the page session. Running an audit that terminates immediately after DOMContentLoaded completely misses shifts triggered when users scroll down to trigger lazy-loaded image galleries, infinite scroll lists, or delayed third-party chat popups.

4. Over-Reserving Slot Heights on Mobile Viewports

Hardcoding desktop dimensions (such as min-height: 250px) on mobile viewports can create enormous, unsightly whitespace blocks on narrow screens, hurting user engagement. Always use responsive CSS media queries or CSS clamp functions (min-height: clamp(100px, 20vw, 250px)) to adapt container reservations dynamically to screen width.


Frequently Asked Questions About Cumulative Layout Shift

What is a good Cumulative Layout Shift (CLS) score?

A good CLS score is 0.10 or less. Scores between 0.10 and 0.25 indicate that your site "Needs Improvement," while any score exceeding 0.25 is classified as "Poor" by Google. To pass the Core Web Vitals assessment, at least 75% of your page visits across both mobile and desktop devices must achieve a score of 0.10 or lower.

Do layout shifts caused by user clicks count toward CLS?

No, provided the shift occurs within 500 milliseconds of the user interaction. The browser assigns a hadRecentInput boolean flag to all layout shifts occurring within 500ms of discrete user events (such as clicks, screen taps, or key presses). These user-induced shifts are completely excluded from your cumulative score. However, asynchronous operations (such as API fetch requests) that resolve after 500ms will be counted as unexpected shifts.

Why is my CLS score higher on mobile devices than on desktop?

Mobile devices frequently exhibit higher CLS scores due to vertical screen constraints. Because mobile viewports are narrower, an un-dimensioned element (such as an ad or image) displaces a significantly larger percentage of the visible viewport, resulting in a higher Impact Fraction. Furthermore, mobile CPU throttling can delay font downloads and client-side hydration, widening the time gap between initial paint and layout stabilization.

How do I identify which specific DOM elements caused a layout shift?

You can observe shifting elements programmatically using the JavaScript PerformanceObserver API with the layout-shift entry type. Inspecting the sources property of each LayoutShift entry returns a list of LayoutShiftAttribution objects containing references to the exact DOM nodes that changed position between frames.

Yes. When combined with explicit HTML width and height attributes, CSS aspect-ratio enables the browser's layout engine to compute the exact bounding box and reserve layout space immediately during initial DOM construction, completely eliminating reflow shifts when image binaries finish downloading.


Summary and Next Steps

Eliminating layout instability requires a disciplined engineering approach: establish explicit HTML dimension attributes on all media assets, match system fallback font metrics to custom web fonts, reserve static bounding slots for dynamic embeds and ads, and animate exclusively via GPU compositor properties (transform and opacity).

Consistently maintaining visual stability across thousands of dynamic URLs and device viewports requires automated continuous testing, which is why a comprehensive BugViso site audit verifies layout stability, asset dimensions, and Core Web Vitals health across your entire crawl graph.


Found this useful? Share it.

See where your site stands

Run a free BugViso audit for SEO, speed, accessibility and AI search readiness — with fixes you can ship today.