Skip to content
What To Fix First

How to fix layout shift on a Shopify store

Layout shift on Shopify stores comes from the theme and the apps, never the platform: an announcement bar that appears after paint, slideshow sections with no fixed height, review and popup app embeds injecting above content, product images output without width and height, and fonts swapping. Each is a theme setting or a Liquid change on a duplicate theme.

By , who built this checker and writes the guides. Published How the tool ranks fixes

Where Shopify shifts come from

The CLS guide explains the metric and the general causes. On Shopify the platform itself is not among them: the server answers quickly, and the image CDN returns correctly sized files. Every shift on a Shopify store comes from the theme, a section setting, an app, or a font. Work through them in this order, and make every change on a duplicate theme (Online Store → Themes → Duplicate) before publishing. Run the Shopify speed test first: it names the element Lighthouse saw move.

1. Announcement bars and shipping bars

The native announcement bar in Dawn and other Online Store 2.0 themes is rendered in the header group, server-side, and does not shift. The ones that do are added by apps — free-shipping progress bars, countdowns, promo strips — which inject a bar at the top of the page after the page has painted, pushing everything down.

  • Check the app’s settings for a position option. “Bottom” or a fixed overlay shifts nothing; “top, push content” shifts everything.
  • If the bar must be at the top, reserve its height in the theme so the content starts below it before the app loads. In theme.liquid or the header section’s CSS:
/* base.css or a custom CSS block — the bar's height, on every breakpoint */
.shopify-section-group-header-group { padding-top: 40px; }
.app-shipping-bar { position: absolute; top: 0; left: 0; right: 0; height: 40px; }
  • Better still, use the theme’s own announcement bar for the message and remove the app. The native bar is static text and costs nothing.

2. Slideshow and image banner height

Dawn’s Slideshow and Image banner sections have a height setting whose default, “Adapt to first image”, means the section has no height until the first image arrives. On a phone that is a large shift on every load.

  • Theme editor → the section → Slide height / Banner height: choose Small, Medium or Large instead of adapting to the image. The section then reserves its height in CSS.
  • On other themes, look for a “section height”, “fixed height” or “aspect ratio” setting on the hero section. If there is none, set one in CSS with aspect-ratio so the box is right before the image loads:
.hero-section { aspect-ratio: 16 / 9; }
@media (max-width: 749px) { .hero-section { aspect-ratio: 4 / 5; } }

Autoplaying slideshows also shift when slides differ in height. Use images with the same dimensions, or a fixed section height.

3. Product and collection images without dimensions

Online Store 2.0 themes output images with image_tag, which writes width and height from the image’s intrinsic size. Older themes, and custom sections written by hand, often use img_url inside a handwritten <img> with no dimensions, so the grid jumps as each product image lands. Replace the pattern:

{%- comment -%} Before: no dimensions, shifts on load {%- endcomment -%}
<img src="{{ product.featured_image | img_url: '600x' }}" alt="{{ product.title | escape }}">

{%- comment -%} After: dimensions and srcset, no shift {%- endcomment -%}
{{ product.featured_image
   | image_url: width: 600
   | image_tag: widths: '300, 600, 900', loading: 'lazy', sizes: '(min-width: 990px) 25vw, 50vw' }}

If you cannot change the tag, add the attributes from the image’s aspect ratio:

<img src="{{ image | img_url: '600x' }}"
     width="600" height="{{ 600 | divided_by: image.aspect_ratio | round }}"
     style="width:100%;height:auto" alt="{{ image.alt | escape }}">

Collection grids with mixed portrait and landscape images shift as well, because rows re-flow when a taller image arrives. Give the card’s image container a fixed aspect-ratio and use object-fit: cover, or choose one ratio for product photography. Dawn’s product card has an “image ratio” setting for exactly this.

4. App embeds that load into nothing

Review stars under a product title, a “customers also bought” row, a size chart button, a wishlist heart: each is an app block or embed that renders a placeholder of zero height, then fills in after its script runs, pushing the price and the add-to-cart button down just as the visitor reaches for them.

  • For app blocks in the theme editor, drag them below the add-to-cart button where a late arrival pushes nothing important. Or reserve their height:
.product__title + .shopify-app-block { min-height: 24px; }   /* star rating row */
#judgeme_product_reviews, .yotpo-main-widget { min-height: 400px; }
  • Look in theme.liquid and layout/ for script tags and <div> placeholders left by apps you have uninstalled. Shopify removes app embeds registered through the app framework; it does not remove code an older app wrote into the theme.
  • Popup apps: set them to overlay (a modal) rather than push, and delay them a few seconds so they never fire during the load.

5. Fonts

Shopify serves its font library from its own CDN with font-display: swap when the theme uses the font_face filter, which Dawn does. The shift is the swap itself: text renders in a fallback, then reflows. Two fixes, in order of effort: preload the body and heading fonts so the swap happens before the first paint, and choose a fallback with matching metrics.

{%- comment -%} theme.liquid, in <head>, before the stylesheet {%- endcomment -%}
{{ settings.type_body_font | font_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous' }}
{{ settings.type_header_font | font_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous' }}

A theme that loads fonts from Google instead of Shopify’s library adds two render-blocking connections on top of the swap; the Google Fonts guide covers replacing it.

6. Cookie and region popups

Shopify’s own cookie banner is a fixed overlay and shifts nothing. Third-party consent apps and geolocation “you seem to be in France” popups often insert at the top. Same rule as the announcement bars: overlay, not push.

Check it worked

Re-run the Shopify speed test on the same page. Lab CLS should be under 0.1 and the layout fix gone from the headline. Because app scripts vary between loads, run it twice. Then publish the duplicate theme, and if LCP is now the headline, the Shopify LCP guide is next.

Common questions

Is layout shift Shopify's fault or my theme's?
The theme's and the apps'. Shopify's platform serves HTML quickly and its image CDN outputs sized images when asked; the shifts come from sections whose height depends on content that arrives late, from app embeds injected after the paint, and from older themes that output images without dimensions. All of those are yours to change on a duplicate theme.
Which apps cause CLS?
Anything that injects a bar or a block above existing content after load: free-shipping bars, announcement and countdown apps, cookie-consent apps set to 'top', review widgets that load into a placeholder with no height, and popups that push rather than overlay. Uninstalling an app does not always remove its snippet from the theme, so check theme.liquid too.
Dawn already sets width and height. Why is my product page shifting?
Dawn's image_tag output does include dimensions. The usual remaining shift on Dawn is the slideshow or image banner section with 'Adapt to first image' height, an announcement bar app, or fonts swapping. On older or heavily customised themes, images output through img_url with a handwritten <img> tag are the first thing to check.
Can I fix it without touching Liquid?
Partly. Section settings (slideshow height, announcement bar position), app settings (bar position, overlay vs push) and font choices are all in the theme editor and app dashboards. Reserving height for an app placeholder or adding dimensions to a custom image tag is a few lines in the theme code editor, on a duplicate theme.
Does the Shopify speed score in the admin show CLS?
It shows a Lighthouse performance score, which folds CLS in with everything else and updates on a lag. It does not tell you which element moved. The Shopify speed test here reports the lab CLS and names the shifting element.

Check a page against this

Runs the same Core Web Vitals test as the Shopify speed test.

Free, no signup, no ads. We keep nothing except the shareable result, for 30 days.

Enter any public URL. You’ll get the one change that recovers the most load time, what it’s worth, and the measurements underneath.

Related guides

All guides