Shopify CLS. Under 0.1.
How to cut Shopify CLS to under 0.1: image dimensions, font loading, ad-slot reservation, and the audit pattern that finds invisible shifts.
Four sources, under 0.1.
Cumulative Layout Shift is the sum of every visible element jump on a Shopify page over the full session. Google's threshold at the 75th percentile of real-user data: 0.1 or less is good, 0.1 to 0.25 needs improvement, over 0.25 is poor. Four sources cover about 90 percent of Shopify CLS failures. Images without explicit width and height attributes shift content down when the image finally loads. Web fonts swapping from fallback to web font (FOUT) shift every line of text on the page. Lazy-loaded sections appearing without reserved space push everything below them down. Sticky banners or popups rendering 200 to 500ms late push the entire page content. Each is a 5 to 15 minute fix once identified. Image dimensions take 1 to 2 hours via theme-wide search-and-replace. Font loading is 1 hour. Lazy-section space reservation is 1 to 2 hours. Banner and popup fixes are 1 to 2 hours. Most Shopify stores hit sub-0.1 CLS in a 4 to 8 hour sprint, the lowest-effort win in Core Web Vitals work.
Every shift, summed and weighted.
Cumulative Layout Shift is the sum of all layout shifts that occur during the lifetime of a page session. A "shift" happens when a visible element changes position between two rendered frames. Google's web.dev CLS guide defines the thresholds: 0.1 or less is "good," 0.1 to 0.25 is "needs improvement," over 0.25 is "poor."
The math: each shift is scored by impact fraction (how much of the viewport the shifting element occupies) times distance fraction (how far it moved relative to the viewport). A header that's 10 percent of the viewport shifting down by 5 percent of the viewport scores 0.005. Stack 20 such shifts in a session and CLS hits 0.1, the failing threshold. The full algorithm is in the layout-shift score specification.
Shifts triggered by user interaction (a click, a button press) within 500ms of the interaction are excluded. This is the "input exclusion window." It protects legitimate UI changes like opening a menu or expanding an accordion from inflating CLS. Shifts caused by async work after that window (lazy-loaded sections appearing, late-rendering widgets, fonts swapping) all count.
Field data over 28 days via the Chrome User Experience Report at the 75th percentile is what affects Search rankings. The 75th-percentile user must see CLS at or below 0.1 for the page to pass. This is stricter than Lighthouse lab data, which only measures the initial page load and misses mid-scroll shifts.
The patterns that break stability.
First, images without explicit width and height attributes. The browser allocates zero vertical space until the image is loaded; when it loads, everything below shifts down by the image's height. On a Shopify product page with 6 product gallery images and a description below, this can produce a 0.2 to 0.5 CLS score on the initial render. The fix is one line per image: width and height attributes in pixels on the img tag.
Second, web fonts swapping in. The browser renders text in a fallback font while the web font loads; when the web font arrives, every text element rerenders at the new font's metrics (different x-height, different character widths). This is the "Flash of Unstyled Text" (FOUT). Every paragraph, heading, and button shifts position. On a content-heavy collection page, FOUT alone can push CLS to 0.15 to 0.3.
Third, lazy-loaded sections rendering without reserved space. Shopify themes often lazy-load below-the-fold sections like "Recently viewed," "You may also like," or "Customer reviews." When these sections render, they push the footer and everything below them down by the section's height. If the user has already scrolled near the footer, the footer jumps away from under their finger as they're about to tap. CLS of 0.1 to 0.2 from this single pattern.
Fourth, sticky banners or popups rendering late. The cookie consent banner that appears 800ms after page load and pushes the header down. The "Sign up for 10 percent off" popup that renders at the top of the page after the initial paint. The free-shipping bar that shows up 500ms in. Each pushes the entire page content down by 40 to 60 pixels.
Five patterns, applied per source.
For images: set width and height on every img element. Use Shopify's image_url Liquid filter to generate the URL at a known size, then pass those dimensions as attributes: <img src="..." width="1200" height="600" alt="...">. CSS handles responsive scaling via max-width: 100%; height: auto; or the modern CSS aspect-ratio property. The browser reserves the correct space immediately even before the image bytes arrive.
For fonts: use font-display: optional in the @font-face declaration. This tells the browser to use the web font only if it's already cached; otherwise, fall back to system fonts and never swap. No FOUT, no shift, no CLS. The trade-off: first-visit users see the fallback font; returning visits see the web font. For most Shopify storefronts the trade-off is correct; brand-critical hero text can be exempted with a preload Link header on those specific weights.
For lazy-loaded sections: reserve space via min-height on the section container or via CSS aspect-ratio on the placeholder. The section's wrapper element has a fixed height before the content arrives; when content renders, it fills the reserved space without pushing anything else. For variable-height content like a reviews widget, use a min-height that's a reasonable estimate; the section settles slightly when content is shorter, but no shift if it's taller.
For banners and popups: render them with reserved space in the DOM from the start, hidden via visibility: hidden or absolute positioning. When the trigger fires, change visibility or transform, not the layout. The space was always there; only the visibility toggles. CLS impact: zero. For cart drawer animations specifically, animate via transform translateX, not via width or display changes; the compositor handles it without layout shift.
The shifts nobody notices.
Most CLS damage comes from shifts the developer never notices in QA. The page looks fine on first load, scrolls fine, and the visible elements behave. But Google's field data shows 0.2 CLS and the page fails Core Web Vitals. The cause is shifts that fire 500ms to 2 seconds after page load, in viewport areas the developer isn't looking at, triggered by async work like reviews widgets, Klaviyo signup forms, and mobile menu measurements.
The reviews widget pattern: the page is "ready" at 1.2s, the user starts scrolling at 1.5s, the Yotpo or Loox script finishes loading review data at 2.1s, and the reviews section renders inline, pushing the footer down by 600 pixels. The user's scroll position now points to a different element than expected; even if they don't see the shift, CLS captured it.
The Klaviyo signup form pattern: a popup or inline form renders late because Klaviyo's targeting logic runs async. The form appears between the H1 and the first paragraph, pushing the paragraph down. The targeting logic is unavoidable but the rendering can be reserved with a placeholder div sized correctly. The mobile menu pattern: on first paint the mobile menu measures its own height via JavaScript to size the open/close transitions; the measurement itself causes a one-frame shift of every element below the menu.
The audit tool: Chrome DevTools Performance Insights panel (the newer one, not the classic Performance tab). It identifies layout shifts and groups them by cause. PageSpeed Insights also has a "Layout Shift" section in the diagnostics that lists specific elements responsible for the largest shifts. Together they surface the invisible shifts that field data would otherwise blame on "the page" without a specific element.
Fix, measure, wait, verify.
The verification cycle for CLS is the same as LCP and INP: measure baseline, apply one fix, measure again, repeat. PageSpeed Insights on mobile is the single most useful tool. Run it before any change, screenshot the CLS score and the diagnostic section listing offending elements. Apply a fix. Run PSI again. The score should move down and the offending elements should drop off the list.
Field data takes longer. The Chrome User Experience Report rolls a 28-day window; a fix shipped today won't fully reflect in CrUX until day 28 or so. The intermediate signal is the web-vitals JavaScript library running in production, which gives you per-session CLS for every real user. Log it to your analytics, watch the 75th-percentile move down within 7 to 14 days of shipping.
The trap: a Lighthouse score that shows CLS 0.05 (passing) while the CrUX field score shows 0.21 (failing). Lighthouse only measures the initial page load. CrUX captures the full session including mid-scroll shifts caused by lazy-loaded sections and late-rendering widgets. Always trust CrUX over Lighthouse for CLS. The fix usually involves shifts that happen outside Lighthouse's measurement window.
Typical engagement: 4 to 8 hours of dev time cuts CLS from 0.25 to under 0.1 on a mid-size Shopify store. Image dimensions are 1 to 2 hours of theme search-and-replace. Font loading is 1 hour of @font-face changes. Lazy-section reservations are 1 to 2 hours of CSS work. Banner and popup fixes are 1 to 2 hours of placeholder DOM work. Most stores hit sub-0.1 in a one-day sprint, making CLS the lowest-effort Core Web Vitals win. Related reading: our Shopify LCP optimization playbook for the largest-element side of Core Web Vitals.
Six answers.
What is CLS and what's the threshold?
Cumulative Layout Shift = sum of all layout shifts over the page lifetime. A shift is when a visible element changes position between frames. Threshold: under 0.1 good, 0.1-0.25 needs improvement, over 0.25 poor. Field data over 28 days is what affects ranking.
What causes Shopify CLS most often?
Four sources cover about 90 percent of cases. Images without explicit width/height attributes. Web fonts swapping in (FOUT). Lazy-loaded sections appearing without reserved space. Banners or pop-ups rendering late and pushing content down. Each is a 5-15 minute fix once identified.
How do I set image dimensions on Shopify?
Use the image_url Liquid filter with width and height parameters, then add explicit width and height attributes on the img tag. CSS aspect-ratio or max-width 100 percent with height auto handles responsive scaling without losing the dimension reservation.
What's the right font-loading strategy for low CLS?
font-display optional eliminates FOUT entirely (browser uses fallback if font not cached). font-display swap is the alternative; the font swaps in but causes a visible shift. Preload critical fonts via Link headers to minimize the swap window.
Why does my CLS look fine in Lighthouse but bad in CrUX?
Lighthouse measures only initial page load. CrUX captures the full session including mid-scroll shifts caused by lazy-loaded sections, late-rendering widgets, and user interactions outside the protective window. Always trust CrUX over Lighthouse for CLS.
How long does it take to bring CLS under 0.1?
Typical engagement: 4-8 hours of dev time. Image dimensions usually take 1-2 hours via theme search-and-replace. Font loading 1 hour. Lazy-section reservations 1-2 hours. Banner/popup fixes 1-2 hours. Most Shopify stores hit sub-0.1 in a one-day sprint.
Pages that stay still.
Our Shopify speed-optimization engagements ship a 2-week Core Web Vitals overhaul: layout-shift audit, image dimensions, font loading, lazy-section reservations, and a 30-day retention report. Scoped quote in 48 hours.
Published .