Skip to main content

Reserve space for app-injected content

Reserve space for app-injected content using App Blocks with Custom CSS to set min-height, or position injected content next to static content to avoid shifting visible content.


The typical sequence is: the browser renders the initial page from theme HTML, app JavaScript loads and executes, the script injects new content into the DOM, and the browser must reflow layout and shift existing content to make room. This shift is recorded as CLS and can cause customers to lose their place or click on the wrong element. Common sources include review and rating widgets, promotional banners, payment option displays, inventory level indicators, and dynamic pricing.

The app widget types that most commonly cause CLS are:

  • Star rating and review widgets below the product title.
  • Trust badge and payment icon rows.
  • Shipping estimate and delivery date widgets.

Cookie consent banners and chat bubbles come from app embed blocks, which Shopify injects before the closing </head> or </body> tag instead of into a section, so the section-scoped CSS below doesn't reach them. They shift layout only when they render in normal flow, so a position: fixed overlay is the fix there rather than reserved space.


Modern apps use app blocks, which you can target with Custom CSS to reserve space before content arrives. Alternatively, position injected content strategically so that shifts don't affect visible content. Add app blocks to the theme, select the parent section, add Custom CSS to the section, and target the .shopify-app-block selector to set min-height or explicit dimensions.

Allow the @app block type in the section schema:

{%- comment -%} In the section schema {%- endcomment -%}
{
"name": "Product",
"blocks": [
{
"type": "@app"
}
]
}

Then add Custom CSS to the section that hosts the block:

/* Reserve space for app blocks inside the product info column */
.product__info-container .shopify-app-block {
min-height: 48px;
}

Shopify wraps every block in an element that carries the shopify-block class, and adds a second class, shopify-app-block, to blocks that come from an app. Target .shopify-app-block so that the reserved space applies to app content and not to the theme's own blocks, which share the broader shopify-block class. Scope the selector to the section or container that holds the block, because a min-height applied to every app block on the page reserves space where none is needed.

The same wrapper also carries an id attribute of shopify-block- followed by the block ID, such as id="shopify-block-1a2b3c4d", so you can reserve space for one specific block instead of every app block in the container. Inspect the rendered DOM in Chrome DevTools to confirm the wrapper before you write the selector, because the element's tag and any extra classes come from the app and the theme.

For different widget types, use different min-height values:

  • Star ratings and small badges: min-height: 24px to 30px.
  • Review summaries and trust badge rows: min-height: 48px to 60px.
  • Full review widgets with content: min-height: 80px to 200px.

  • Position injected content next to static content of equal or greater height. The shift still occurs, but because the surrounding content is taller, nothing visible moves.
  • Place injected content at the bottom of a section or outside the initial viewport. Shifts below the viewport don't count toward CLS.
  • Use a placeholder skeleton, such as a gray box or shimmer animation, that matches the expected widget dimensions. This signals that content is loading while preventing layout shift.

Anchor to Reserve space for a review widget on the product pageReserve space for a review widget on the product page

A star rating app block sits under the product title, above the price. It renders about 28 px tall on desktop and wraps to two lines on mobile. Reserve the space it needs at each breakpoint, and target the specific block so that other app blocks in the same container aren't padded:

/* Every app block in the product info column */
.product__info-container .shopify-app-block {
min-height: 28px;
}

/* The rating block specifically, which wraps on small screens */
#shopify-block-1a2b3c4d {
min-height: 28px;
}

@media screen and (max-width: 749px) {
#shopify-block-1a2b3c4d {
min-height: 56px;
}
}

Pair the reserved space with a placeholder so that the gap reads as loading content rather than a rendering bug. :empty matches only a wrapper with no child nodes at all, including whitespace, so confirm in DevTools that the placeholder disappears after the app renders:

#shopify-block-1a2b3c4d:empty {
background: linear-gradient(90deg, #f4f4f4 25%, #ebebeb 50%, #f4f4f4 75%);
border-radius: 4px;
}

To determine the correct value:

  1. Install the app on a development store.
  2. Open Chrome DevTools, and inspect the rendered app widget.
  3. Note the offsetHeight of the widget's container element.
  4. Use that value (or slightly larger) as your min-height.
  5. Test on mobile too, because many widgets render at different heights on smaller screens.

Anchor to Any framework's container elementsAny framework's container elements

The min-height pattern applies to any framework's container elements. If you're rendering content with a JavaScript framework inside a Liquid theme, then set the container's dimensions before the framework renders content:

.lazy-component-container {
min-height: 100px;
}

  • The Chrome DevTools Performance panel shows layout shifts. Look for the Layout Shifts track, or use its Insights tab for an automated analysis.
  • Use the Rendering tab and Layout Shift Regions to visually highlight shifted content in real time.
  • Visually observe with network throttling enabled.
  • Compare before and after the min-height implementation.


Was this page helpful?