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 Jose Pollman, 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.liquidor 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-ratioso 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.liquidandlayout/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.
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
- How to fix layout shift — Content jumps around while the page loads.
- How to fix a slow LCP on a Shopify store — The hero image, then the apps, then the Liquid.
- How to fix layout shift in WordPress — Unsized images, late fonts and injected bars, each with its setting.