CLS Webflow

Fix CLS in Webflow

Cumulative Layout Shift (CLS) in Webflow sites is most often caused by four things: images and media without fixed dimensions causing content to jump when they load, Interactions that animate layout properties (height, margin, padding) rather than transform-only properties, CMS Collection List images that expand their containers, and fonts swapping from system fallbacks to custom typefaces. Webflow sites built with rich animations are particularly susceptible because designers using the Interactions panel may not distinguish between layout-property animations (which cause CLS) and composited-property animations (which do not). This guide focuses on changes you can make inside the Webflow Designer without exporting code.

Expected results

Before

0.26

CLS (Poor) -- unsized images, layout-shifting animations, unstable nav

After

0.03

CLS (Good) -- reserved image space, transform-only interactions, fixed header

Step-by-step fix

Set dimensions on all images in Webflow Designer

Every image in Webflow should have an explicit width and height set in the image element's settings. When the browser encounters an image without known dimensions, it allocates no space for it initially. When the image file arrives and the browser learns its natural dimensions, it inserts the image into the layout -- pushing all surrounding content out of the way. This is a direct CLS event. In the Webflow Designer, select any image element and check the W (width) and H (height) fields in the Style panel. Set both to fixed pixel values that match the image's display size. For images in responsive grids where the size varies by breakpoint, use the CSS aspect-ratio property on the image's parent div instead: set the div's aspect ratio and make the image fill 100% of its parent. This reserves the proportionally-correct space at every viewport width without requiring fixed pixel dimensions.

CSS -- Webflow Custom Code: image sizing patterns
/* Pattern 1: Fixed dimensions for non-responsive images
   Set width and height directly in Webflow Designer Style panel */

/* Pattern 2: aspect-ratio for responsive images
   In Webflow Designer: add class 'image-wrapper' to parent div
   Set width to 100%, then add custom CSS: */
.image-wrapper {
  aspect-ratio: 16 / 9;   /* matches image natural proportions */
  overflow: hidden;
  background-color: #f3f4f6;  /* placeholder color during load */
  width: 100%;
}

.image-wrapper img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Pattern 3: Square thumbnails in CMS grids */
.cms-card-image-wrap {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  width: 100%;
}

/* Add this CSS in Webflow Project Settings > Custom Code > Head
   or in a style tag within the page's Custom Code section */

Avoid Webflow interactions that change layout

The key distinction for CLS-safe animations is between composited properties and layout properties. Composited properties -- transform (translate, scale, rotate) and opacity -- are handled by the GPU's compositing layer and do not affect document layout. Layout properties -- height, width, top, left, margin, padding -- require the browser to recalculate the layout of the entire document, which counts as a layout shift. In Webflow's Interactions panel, inspect each animation tween. If any tween changes height from 0 to auto, width from 0 to a value, or adjusts margin or padding, replace it with a transform equivalent: a height reveal can become a scale(Y) transform, a slide-in can use translateX(), and an expand can use scale(). The visual result is nearly identical, but the browser impact is dramatically different.

CSS -- CLS-safe vs CLS-causing animation patterns
/* BAD: height animation causes CLS (layout recalculation) */
/* Webflow Interaction tween: height 0px -> height auto */
/* This forces a full document reflow on every animation frame */

/* GOOD: scaleY animation (composited, no CLS) */
/* Webflow Interaction tween:
   Start: transform scaleY(0), opacity 0, transform-origin bottom
   End:   transform scaleY(1), opacity 1 */

/* Custom CSS equivalent (add to element via Custom Attributes): */
.accordion-content {
  transition: transform 0.3s ease, opacity 0.3s ease;
  transform-origin: top center;
}
.accordion-content[aria-hidden="true"] {
  transform: scaleY(0);
  opacity: 0;
}
.accordion-content[aria-hidden="false"] {
  transform: scaleY(1);
  opacity: 1;
}

/* BAD: margin animation causes layout shift */
/* Webflow Interaction: margin-top 20px -> margin-top 0px */

/* GOOD: translateY animation (no layout shift) */
/* Webflow Interaction tween:
   Start: transform translateY(20px), opacity 0
   End:   transform translateY(0), opacity 1 */

Reserve space for CMS collection lists

Webflow CMS Collection Lists render their content server-side and include it in the initial HTML, so they do not cause the same kind of CLS as client-side data fetching. However, CMS collection grids often cause layout shift in a subtler way: the Collection List Wrapper element has no explicit height and its grid children have no reserved image space. When the page first renders, the wrapper takes up minimal space, then expands to its full height as images load and text content is laid out. If there is content below the collection grid -- a testimonials section, a footer -- that content shifts upward as the grid settles. Fix this by setting a min-height on the Collection List Wrapper in the Webflow Designer Style panel. Calculate the minimum expected height based on your grid's column count and the minimum number of items. Combine this with the aspect-ratio image wrapper from step 1.

CSS -- CMS Collection List stable sizing
/* Apply to Collection List Wrapper element (add class 'cms-grid-wrap') */
.cms-grid-wrap {
  /* Reserve minimum height for a 2-row grid of 3 columns */
  /* Each row is ~400px -- adjust to your design */
  min-height: 800px;

  /* Use CSS Grid for stable layout (set in Webflow Designer) */
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  align-items: start;  /* prevents stretching that causes shifts */
}

/* Collection item: stable card structure */
.cms-card {
  /* No min-height needed if image aspect-ratio is set */
  display: flex;
  flex-direction: column;
}

/* On mobile: single column, shorter min-height */
@media (max-width: 767px) {
  .cms-grid-wrap {
    grid-template-columns: 1fr;
    min-height: 400px;
  }
}

/* Webflow Designer: select Collection List Wrapper
   Style panel > Min Height > set value matching calculation above */

Handle custom fonts without layout shift

Font-induced CLS happens when the browser first renders text using a system fallback font (such as Arial or Times New Roman), then replaces it with the loaded custom web font. Because these fonts have different character widths, letter spacing, and line heights, the swap causes all text on the page to reflow -- every heading shifts, every paragraph expands or contracts, and the surrounding layout adjusts. In Webflow, fonts added through the Typography settings in the Designer are loaded through either Google Fonts or Adobe Fonts. Both of these services support font-display:swap, but Webflow does not always apply it automatically. Add an override in the Head Custom Code section. To further reduce the visual impact of the font swap, choose a fallback font that closely matches the web font's proportions. Tools like Font Style Matcher let you adjust fallback font metrics to minimize the visible shift.

HTML + CSS -- Site Settings > Custom Code > Head
<!-- Override font-display for Webflow-loaded Google Fonts -->
<style>
  /* Override Google Fonts @font-face to add font-display:swap */
  @font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url(https://fonts.gstatic.com/s/inter/v13/...) format('woff2');
  }

  /* Fallback font matched to Inter's metrics */
  :root {
    --font-body: 'Inter', 'Arial', sans-serif;
  }

  /* Metric-adjusted fallback reduces visual reflow */
  /* Use Font Style Matcher to calculate these values */
  @font-face {
    font-family: 'Arial-adjusted';
    src: local('Arial');
    size-adjust: 107%;
    ascent-override: 92%;
    descent-override: 24%;
  }

  body {
    font-family: 'Inter', 'Arial-adjusted', 'Arial', sans-serif;
  }
</style>

<!-- Preload the most critical font file -->
<link
  rel="preload"
  as="font"
  href="https://fonts.gstatic.com/s/inter/v13/[font-hash].woff2"
  type="font/woff2"
  crossorigin
>

Stabilize navigation and header height

Many Webflow sites use a navigation pattern where the header starts transparent over the hero section and transitions to a solid background color when the user scrolls. This effect is achieved through a Webflow Interaction triggered on page scroll. If this transition changes the header's height -- for example, the logo shrinks, padding reduces, or a secondary navigation row hides -- the height change causes layout shift in the content below. Additionally, if your navigation contains dynamic elements such as a cart item count or a user login status, these are often injected after the initial HTML by JavaScript and can cause the header to change height. Fix this by setting a fixed pixel height on the Navbar element in Webflow Designer. If the scroll-triggered header style change must occur, ensure it only changes background color, not height, padding, or font size. Use contain: layout in CSS to further isolate the header's layout changes from the rest of the page.

CSS -- Webflow navigation stability
/* In Webflow Designer: select Navbar component
   Style panel > Height > Fixed, e.g., 72px
   This prevents height changes during scroll interactions */

/* Custom CSS for scroll interaction (add to Custom Code > Head) */
.navbar {
  height: 72px;              /* match your Webflow Designer setting */
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 999;
  contain: layout;           /* layout changes stay inside navbar */
  transition: background-color 0.3s ease; /* only transition bg color */
}

/* Webflow interaction: Page Scroll trigger
   Only change background-color -- NOT height, padding, or font-size */
.navbar.scrolled {
  background-color: white;
  /* Do NOT add: height change, padding change, border that adds space */
}

/* Reserve space for the fixed navbar in page body */
body {
  padding-top: 72px;         /* prevents content from hiding behind navbar */
}

/* For mobile breakpoint (Webflow breakpoint: Mobile Portrait) */
@media (max-width: 479px) {
  .navbar {
    height: 60px;
  }
  body {
    padding-top: 60px;
  }
}

Quick checklist

  • All images have explicit dimensions set in Webflow Designer or aspect-ratio on their wrapper divs
  • All Interactions use only transform and opacity -- no height, width, margin, or padding tweens
  • CMS Collection List Wrappers have min-height set to prevent page shift when content loads
  • Fonts use font-display: swap and a metric-adjusted fallback font is specified
  • Navbar has a fixed pixel height and scroll interaction only changes background color
  • contain: layout applied to navbar and any element that changes independently of page content

Frequently asked questions

The most common CLS sources in Webflow are images without set dimensions that push content down when they load, entrance animations that transition height, width, margin, or padding values, CMS Collection List images that expand their containers before the layout is stable, custom fonts swapping from a fallback typeface to the web font, and navigation headers that change size when they transition from transparent to solid on scroll.

Webflow animations that change layout properties (height, width, top, left, margin, padding) cause CLS because they trigger layout recalculation. Animations using only transform (translate, scale, rotate) and opacity do not cause CLS because these properties are composited by the GPU without affecting document layout. Audit your Interactions panel and replace any layout-property animations with transform equivalents to eliminate animation-driven CLS.

Webflow CMS data is embedded in the page's initial HTML at publish time, so it does not cause CLS in the same way as client-side data fetching. However, CMS Collection List images without explicit dimensions will still cause CLS when they load. The main CMS-related CLS source is Collection List Wrapper elements that start with no height and expand as content is laid out during initial render. Setting a min-height on the Collection List Wrapper resolves this.

Use Webflow's native Google Fonts or Adobe Fonts integration where possible. Add font-display: swap overrides in the Head Custom Code section for any web fonts to prevent invisible text during load. Choose a fallback font with similar x-height and character width to the web font to minimize the visual reflow when the web font loads. Preload the most critical font file using rel="preload" in the Head Custom Code section.

Publish your Webflow site and test the published URL (not the Webflow preview) using Google PageSpeed Insights or Chrome DevTools Lighthouse -- the designer preview environment behaves differently from the published site. In Chrome DevTools, open the Rendering panel and enable Layout Shift Regions to see shifting elements highlighted in real time as you scroll. For field data from real visitors, add the web-vitals JavaScript library via Custom Code to capture and report actual CLS values to your analytics.

Set up real-user monitoring using the web-vitals JavaScript library (1.5KB). Send CLS data to your analytics platform (Google Analytics 4, custom endpoint). The attribution build identifies exactly which element caused each layout shift. For Webflow, also monitor CLS after route transitions, as client-side navigation can trigger additional shifts not captured in initial page load.

Continue learning