---
title: Reserve space for app-injected content
description: >-
  Reserve space for app-injected content using App Blocks with `min-height` CSS,
  or position injected content next to static content, so the browser doesn't
  shift existing content when the app's widget loads.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/reserve-space-app-injected
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/reserve-space-app-injected.md
api_name: liquid
---

# 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.

***

## Why

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](https://web.dev/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](https://shopify.dev/docs/apps/build/online-store/theme-app-extensions/configuration#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.

***

## How

Modern apps use [app blocks](https://shopify.dev/docs/storefronts/themes/architecture/blocks/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:

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

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

```css
/* 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`.

### Design alternatives

* 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.

***

## Examples

### Reserve 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:

```css
/* 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:

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

### Measuring the right `min-height`

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.

### Any 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:

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

***

## Testing

* The [Chrome DevTools Performance panel](https://developer.chrome.com/docs/devtools/performance) 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](https://developer.chrome.com/docs/devtools/network#throttle) enabled.
* Compare before and after the `min-height` implementation.

***

## References

* [How to optimize Cumulative Layout Shift (CLS) on Shopify sites](https://performance.shopify.com/blogs/blog/how-to-optimize-cumulative-layout-shift-cls-on-shopify-sites)
* [Prevent dialogs from hijacking LCP](https://shopify.dev/docs/storefronts/themes/best-practices/performance/prevent-popups-hijacking-lcp)
* [Prevent image layout shift](https://shopify.dev/docs/storefronts/themes/best-practices/performance/prevent-image-layout-shift)

***
