---
title: Don't use CSS background-image for LCP content
description: >-
  Replace CSS background images with `<img>` tags for hero and LCP images so the
  browser's preload scanner can discover them during initial HTML parsing.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-background-images-hero
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-background-images-hero.md
api_name: liquid
---

# Don't use CSS background-image for LCP content

Use `<img>` tags for hero and LCP images instead of CSS background images to enable early discovery by the browser's preload scanner.

***

## Why

CSS background images are discovered late in the rendering process, creating a waterfall delay:

1. HTML downloads and parses.
2. The CSS file is discovered and downloads.
3. The CSS parses.
4. The background image URL is discovered.
5. The image finally starts downloading.

Using an `<img>` tag lets the browser's preload scanner discover images during the initial HTML parse, starting the image download immediately in parallel with CSS.

The impact is especially significant when the background image is in an external CSS file, because CSS download latency adds to the discovery delay.

### Additional benefits of `<img>` tags

* You can use `fetchpriority="high"` to prioritize the LCP image.
* You can use `<link rel="preload">` if needed.
* Responsive image support with `srcset` and `sizes` is better.
* It's easier to add `width` and `height` for CLS prevention.
* It works with Shopify's `image_tag` filter and automatic optimization.

### When background images are acceptable

* Decorative patterns or textures.
* Non-LCP content elements.
* Elements outside the initial viewport.
* Small UI elements where discovery latency doesn't matter.
* Data URIs or inline SVGs, because there's no network request.

***

## How

Use an `<img>` tag with absolute positioning to achieve the same visual effect as a background image. Load the image conditionally based on section position using `section.index`.

Write the condition as a positive `section.index > 2` test rather than `section.index <= 2`. `section.index` is `nil` in the online store editor, in static sections, and in the [Section Rendering API](https://shopify.dev/docs/api/section-rendering), and comparisons against `nil` are falsey, which would send the hero to the lazy branch. For the full explanation, refer to [Load above-the-fold and below-the-fold sections differently with `section.index`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-section-index).

HTML structure:

```liquid
<section class="hero">
  {% unless section.index > 2 %}
    {{ section.settings.image
      | image_url: width: 2000
      | image_tag:
          loading: 'eager',
          fetchpriority: 'high',
          widths: '600, 900, 1200, 1600, 2000',
          sizes: '100vw',
          class: 'hero__image'
    }}
  {% else %}
    {{ section.settings.image
      | image_url: width: 2000
      | image_tag:
          loading: 'lazy',
          widths: '600, 900, 1200, 1600, 2000',
          sizes: '100vw',
          class: 'hero__image'
    }}
  {% endunless %}
  <div class="hero__content">
    <h1>{{ section.settings.title }}</h1>
  </div>
</section>
```

CSS for positioning:

```css
.hero {
  position: relative;
  min-height: 400px;
}


.hero__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}


.hero__content {
  position: relative;
  z-index: 1;
}
```

The image needs no `z-index`, because `.hero__content` already stacks above it. Don't add a negative one: `position: relative` alone doesn't make `.hero` a stacking context, so `z-index: -1` escapes it and can paint behind a section or wrapper background, leaving the LCP element blank. [Dawn](https://github.com/Shopify/dawn/blob/main/assets/section-image-banner.css) and [Horizon](https://github.com/Shopify/horizon/blob/main/sections/hero.liquid) both position their hero media this way.

***

## Examples

### Anti-pattern: inline background image

```liquid
{%- comment -%} Anti-pattern: don't do this {%- endcomment -%}
<section class="hero" style="background-image: url({{ section.settings.image | image_url: width: 2000 }});">
  <h1>{{ section.settings.title }}</h1>
</section>
```

### Anti-pattern: external CSS background image

```css
/* Anti-pattern: don't do this */
.hero {
  background-image: url("hero.jpg");
  background-size: cover;
}
```

### Recommended: `<img>` tag with positioning

Both anti-patterns convert to the same markup. When the hero is a static section that always renders at the top of the page, the image is unconditionally eager and no `section.index` test is needed:

```liquid
<section class="hero">
  {{ section.settings.image
    | image_url: width: 2000
    | image_tag:
        loading: 'eager',
        fetchpriority: 'high',
        widths: '600, 900, 1200, 1600, 2000',
        sizes: '100vw',
        class: 'hero__image'
  }}
  <div class="hero__content">
    <h1>{{ section.settings.title }}</h1>
  </div>
</section>
```

When a merchant can move the section anywhere on the page, use the `section.index` version in [How](#how) instead. Both versions need the CSS from that section so that the image fills the section and the content stacks above it.

***

## Testing

Use the [Chrome DevTools Network panel](https://developer.chrome.com/docs/devtools/network) to compare before and after:

* Open the DevTools **Network** tab and throttle to **Slow 3G** to see the waterfall clearly.
* Look at when the hero image request starts in the waterfall.
* Background image: starts after the CSS file completes.
* `<img>` tag: starts immediately during the HTML parse.

***

## References

* [Mark the LCP image with `fetchpriority="high"`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/set-fetchpriority-high-on-lcp-image)
* [Never lazy-load the LCP image](https://shopify.dev/docs/storefronts/themes/best-practices/performance/never-lazy-load-lcp-image)
* [Use `preload` resource hints sparingly](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-preload-resource-hints-sparingly)
* [Use responsive images](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-responsive-images)
* [Load above-the-fold and below-the-fold sections differently with `section.index`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-section-index)
* [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag) filter
* [`image_url`](https://shopify.dev/docs/api/liquid/filters/image_url) filter
* [`section.index`](https://shopify.dev/docs/api/liquid/objects/section#section-index)
* [The stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context) on MDN

***
