UI/UX Atlas
Responsive & Platform Intermediate

Core Web Vitals & CLS Prevention

Master Google's performance metrics that directly connect layout stability and load speed to real user experience and search ranking.

8 min read

The full lesson

Performance is a design problem. When a page jumps, flickers, or keeps users waiting, that is a failure of the interface — not just a backend issue. Google’s Core Web Vitals gives designers and engineers a shared language. It defines three measurable signals that directly relate to whether users abandon a page, complete a purchase, or can access content at all. Understanding these metrics from a design perspective — not just a DevTools tab — is what separates practitioners who ship polished interfaces from those who hand work off and hope.

What Are Core Web Vitals?

Core Web Vitals are real-world experience metrics. Google uses them as search ranking signals and collects them through the Chrome User Experience Report (CrUX), which gathers data across billions of real page loads. As of 2026, there are three primary metrics:

MetricWhat it measuresGood thresholdPoor threshold
LCP — Largest Contentful PaintHow fast the main content renders≤ 2.5 sgreater than 4.0 s
INP — Interaction to Next PaintResponsiveness to every interaction≤ 200 msgreater than 500 ms
CLS — Cumulative Layout ShiftVisual stability; unexpected movement≤ 0.1greater than 0.25

INP replaced FID (First Input Delay) as an official Core Web Vital in March 2024. If you are still optimising FID, you are working against a retired metric.

CLS In Depth: What Causes Layout Shift

CLS scores every unexpected movement of visible elements on a page, across the whole page lifespan. Each shift event is calculated by multiplying two things: the impact fraction (how much of the viewport moved) by the distance fraction (how far it moved). These scores add up across all shift events that were not triggered by a user action in the previous 500 ms.

Common design-level causes:

  • Images without declared dimensions — the browser allocates zero space until the image loads, then reflowing content around it
  • Ad slots, embeds, and iframes with no reserved space
  • Fonts injecting FOUT/FOIT (Flash of Unstyled Text or Flash of Invisible Text) — the fallback font has different line heights and character widths, so text reflows when the web font swaps in
  • Dynamic content injected above the fold — cookie banners, chat widgets, and notification bars that appear after the initial paint
  • Animations that change layout propertieswidth, height, top, left, or margin all trigger a reflow

A CLS of 0.1 feels imperceptibly stable. A CLS of 0.25 or higher means visible content is jumping far enough to cause misclicks — a user taps “Cancel” because “Submit” shifted into that spot just before they tapped.

Reserving Space for Media

The single most impactful fix is giving every image and video explicit dimensions. This lets the browser pre-allocate layout space before the asset downloads.

<!-- Modern: native aspect-ratio hint via width/height attributes -->
<img src="hero.webp" width="1200" height="630" alt="..." loading="lazy" />
/* CSS enforces the ratio fluidly */
img {
  width: 100%;
  height: auto;
}

The browser reads the width and height HTML attributes to compute an intrinsic aspect ratio. It then reserves the correct vertical space before the image loads. This is the most common CLS fix and requires zero JavaScript.

For responsive images using srcset, the same rule applies. The width and height on the img element still set the aspect ratio even when the rendered size changes.

Aspect-ratio container pattern for iframes and embeds:

.embed-wrapper {
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
}

.embed-wrapper iframe {
  width: 100%;
  height: 100%;
}

This reserves the correct vertical space so the iframe never causes a layout shift on load.

Font Loading and FOUT

Web fonts can cause a type of CLS called metric-mismatch layout shift. The fallback font occupies different line heights and word widths than the web font. When the web font finally swaps in, surrounding text reflows.

Two modern tools address this:

font-display: optional — the browser uses the cached font if it arrives before the first paint. Otherwise it falls back and never swaps. This eliminates layout shift at the cost of the font missing on first-visit cold loads. Use it when the web font is an enhancement, not a brand requirement.

size-adjust and ascent-override descriptors — these tune a fallback font’s metrics to match the web font. If a reflow does happen, the visible shift is zero:

@font-face {
  font-family: "FallbackForInter";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
}

Tools like fontaine and the Nuxt/Next.js font modules generate these overrides automatically. Knowing how to tune them manually is a useful fallback skill.

Dynamic Injections: Banners, Ads, and Toasts

Cookie consent banners, chat widgets, and promotional bars are frequent CLS offenders. They are injected after the initial paint and push content down. Design strategies:

  • Reserve space in the layout from the start — use a placeholder slot with the banner’s fixed height so the content shift has already happened before paint
  • Slide in from a fixed overlay position rather than altering document flow — a fixed or sticky element entering from the top or bottom edge of the viewport does not shift other content
  • Defer below-fold injections — ads and widgets outside the initial viewport do not contribute to CLS; inject them lazily

For toast notifications and system messages: anchor them to a fixed overlay layer (position: fixed) and avoid inserting them into the document flow. Users perceive a floating toast as an overlay, not as content that shifts.

LCP: The Designer’s Checklist

LCP measures when the largest visible element finishes rendering. On most marketing and editorial pages, that is a hero image or a large heading. Your design decisions directly affect this number:

  • Avoid lazy-loading the LCP imageloading="lazy" on a hero image is a common mistake that delays LCP by 200–500 ms on average
  • Use fetchpriority="high" on the LCP image to tell the browser to prioritise it over other resources
  • Choose the right format — AVIF offers the best compression; WebP is the safe fallback with near-universal support
  • Avoid CSS background images for LCP elements — the browser cannot discover them until the CSS is parsed; inline img elements in HTML are discovered much earlier
  • Inline critical CSS in head — styles that affect above-the-fold layout should not require a render-blocking stylesheet fetch

LCP is also affected by server response time (TTFB — Time to First Byte). If TTFB exceeds 800 ms, no amount of client-side optimisation will consistently hit the 2.5 s threshold.

INP: Interaction Responsiveness

INP replaced FID to capture the full interaction lifecycle, not just the first interaction. It measures the time from a user gesture (click, tap, or keyboard input) to when the browser next paints a visual response — the “next paint” after the interaction handler runs.

Design implications:

  • Long tasks on the main thread are the primary INP killer; heavy JavaScript that runs in response to a click blocks the next paint
  • Skeleton screens and optimistic UI reduce perceived INP — showing an immediate state change (a button entering a loading state) gives the user feedback within the 200 ms window, even if the full result takes longer
  • Debounce and throttle input handlers on search fields and filters to avoid firing expensive operations on every keystroke
/* Immediate visual feedback via CSS — no JS needed for the first paint */
button:active {
  transform: scale(0.97);
  opacity: 0.85;
}

The CSS :active state fires synchronously on press. Using transform and opacity — compositor-only properties — provides an instant response without blocking the main thread.

Compositor-Only Animations and CLS

Animations that change layout properties (width, height, margin, padding, top, left) trigger a full reflow and repaint on every frame. This consumes main-thread time and can cause layout shifts. The modern best practice is to restrict motion to compositor-only properties: transform and opacity.

/* Outdated — triggers layout reflow, can cause CLS, slow on low-end devices */
.panel--open {
  height: 300px; /* was 0 */
}

/* Modern — compositor-only, zero CLS contribution */
.panel--open {
  transform: scaleY(1);
  transform-origin: top;
}

For expanding or collapsing containers where the natural height must be honoured, the calc-size() CSS function (shipping in Chrome 129+ with wider support in 2025–26) enables animating height: auto without JavaScript measurement hacks.

Do

Declare explicit width and height attributes on every img element. Reserve space for ads and embeds using aspect-ratio containers. Use position: fixed for banners and toasts that should not shift document flow. Restrict animations to transform and opacity. Use fetchpriority=“high” on the LCP image and never lazy-load it.

Don't

Lazy-load hero or above-the-fold images. Inject banners or chat widgets into document flow after initial paint. Use CSS background-image for LCP-candidate elements. Animate height, width, top, or margin for transitions. Forget to declare font fallback metrics, leaving FOUT as a CLS source.

Measuring and Monitoring in Practice

Measurement tools by fidelity:

ToolData typeBest for
Chrome DevTools Performance panelLabDiagnosing specific shift events
Lighthouse CILabCI gate on regressions
PageSpeed InsightsField + LabReal-user 75th percentile
CrUX Dashboard (Looker Studio)FieldTrend monitoring over time
web-vitals.js libraryFieldCustom RUM reporting

The web-vitals JS library (maintained by the Chrome team) exposes onCLS, onLCP, and onINP callbacks that feed into any analytics pipeline. Hooking these into your product analytics gives you field data segmented by page, device class, and release version — far more actionable than one-off audits.

Debugging CLS in DevTools:

  1. Open the Performance panel and record a page load.
  2. In the summary lane, look for red “Layout Shift” markers.
  3. Click a marker to expand the “Experience” track — it shows the shifted element, impact rectangle, and contributing sources.

The Layout Instability API (PerformanceObserver with layout-shift entry type) also lets you log shift events with their source elements to the console during development.

Responsive Design Implications

Core Web Vitals scores vary by device class. Mobile field data is almost always worse than desktop — lower CPU, slower networks, and aggressive memory constraints affect all three metrics. Designing responsively for performance means:

  • Testing on real mid-range Android devices, not flagship phones or desktop Chrome
  • Responsive images with srcset and sizes so mobile receives appropriately-sized assets — a 1200 px hero served to a 390 px screen is both slow (LCP) and a potential CLS source if sizes cause a reflow
  • Container queries for component-level layout avoid shipping complex viewport-based JavaScript that runs on every resize, keeping the main thread clear for INP
  • Intrinsic CSS Grid (minmax, auto-fill, auto-fit) collapses layouts gracefully without JavaScript measurement, removing a class of INP-harming resize observers

The connection between responsive design and Core Web Vitals is direct: fluid, content-driven layouts that do not depend on JavaScript for basic adaptation are inherently more stable than layouts that measure, calculate, and then reflow.