---
title: Use the picture element when you have separate mobile and desktop images
description: >-
  Replace CSS-toggled mobile and desktop hero image pairs with a single picture
  element so the browser downloads only the image that matches the viewport.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-picture-for-art-directed-heroes
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-picture-for-art-directed-heroes.md
api_name: liquid
---

# Use the picture element when you have separate mobile and desktop images

Replace CSS-toggled mobile and desktop hero image pairs with a single `<picture>` element so the browser downloads only the image that matches the viewport.

***

## Why

Many Shopify themes render two separate `<img>` tags for the hero, one for desktop and one for mobile, and toggle visibility with CSS `display: none` at a breakpoint. The browser can't know that one of the images is hidden, so it downloads both. On a mobile device, this means the desktop hero (often a wide, high-resolution image) downloads alongside the mobile hero, wasting bandwidth and competing for connection resources.

A second problem compounds the first: `fetchpriority="high"` is often applied only to the desktop image using the Liquid `image_tag` filter, because the mobile image is rendered in a separate code path that omits it. On mobile, typically 60 to 75% of storefront traffic, the LCP image downloads at default priority instead of high, adding hundreds of milliseconds to LCP.

The `<picture>` element solves both problems. The browser evaluates the `<source media="...">` conditions and downloads only the matching image. `fetchpriority="high"` goes on the inner `<img>` fallback element and applies to whichever source the browser selects.

***

## How

Replace the two-image pattern with a `<picture>` element. The mobile source comes first so that the browser picks it when the media query matches. The desktop source is the fallback.

```liquid
{%- comment -%} Before: two img tags toggled by CSS {%- endcomment -%}
{{ desktop_image | image_url: width: 2560 | image_tag:
    class: 'hero__image hero__image--desktop',
    fetchpriority: fetch_priority,
    sizes: sizes
}}
{% if mobile_image != blank %}
  {{ mobile_image | image_url: width: 1200 | image_tag:
      class: 'hero__image hero__image--mobile',
      sizes: sizes
  }}
{% endif %}


{%- comment -%} After: single picture, browser downloads only the matching source {%- endcomment -%}
<picture>
  {% if mobile_image != blank %}
    <source
      media="(max-width: 749px)"
      srcset="{{ mobile_image | image_url: width: 600 }} 600w,
             {{ mobile_image | image_url: width: 800 }} 800w,
             {{ mobile_image | image_url: width: 1000 }} 1000w,
             {{ mobile_image | image_url: width: 1200 }} 1200w"
      sizes="100vw"
      width="{{ mobile_image.width }}"
      height="{{ mobile_image.height }}"
    >
  {% endif %}
  <source
    media="(min-width: 750px)"
    srcset="{{ desktop_image | image_url: width: 1200 }} 1200w,
           {{ desktop_image | image_url: width: 1600 }} 1600w,
           {{ desktop_image | image_url: width: 2000 }} 2000w,
           {{ desktop_image | image_url: width: 2560 }} 2560w"
    sizes="100vw"
    width="{{ desktop_image.width }}"
    height="{{ desktop_image.height }}"
  >
  {{ desktop_image | image_url: width: 2560 | image_tag:
      fetchpriority: fetch_priority,
      loading: 'eager',
      sizes: '100vw',
      class: 'hero__image'
  }}
</picture>
```

Key points:

* `fetchpriority="high"` goes on the `<img>` element only. It applies to whichever `<source>` the browser selects. Don't put it on `<source>` because the attribute isn't valid there.
* `width` and `height` on each `<source>` prevent layout shift. The browser uses the dimensions of the matched source to reserve space before the image loads.
* The `<img>` fallback uses the desktop image.
* Remove the CSS rules that toggle `.hero__image--mobile` and `.hero__image--desktop` visibility. They're no longer needed.

When the merchant hasn't uploaded a separate mobile image, the desktop source is the only one and the `<picture>` degrades gracefully to a single responsive image.

***

## Examples

### Detecting the anti-pattern

In Chrome DevTools, check the **Network** panel at both mobile and desktop viewport widths. If two hero-sized images download on a single page load and the page uses CSS classes like `--mobile` and `--desktop` with `display: none` media queries, then the theme is using the two-image pattern.

In the theme code, look for two `image_tag` calls in the hero section, one with a `--desktop` class and one with `--mobile`. Check whether both calls include `fetchpriority:`. Frequently only the desktop one does.

***

## Testing

* In the [Chrome DevTools Network panel](https://developer.chrome.com/docs/devtools/network), at a mobile viewport, verify that only one hero image downloads. Enable the **Priority** column and confirm the hero image loads at High priority.
* Toggle between mobile and desktop viewports in Device Mode and verify the correct image appears at each breakpoint.
* Check that `width` and `height` on the `<source>` elements prevent layout shift during image load.

***

## References

* [`<picture>` element, MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
* [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag) filter
* [`image_url`](https://shopify.dev/docs/api/liquid/filters/image_url) filter
* [Use responsive images](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-responsive-images)
* [Prevent image layout shift](https://shopify.dev/docs/storefronts/themes/best-practices/performance/prevent-image-layout-shift)
* [Mark the LCP image with `fetchpriority="high"`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/set-fetchpriority-high-on-lcp-image)

***
