Skip to main content

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.


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.


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.

{%- 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.


Anchor to Detecting the anti-patternDetecting 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.


  • In the Chrome DevTools Network panel, 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.


Was this page helpful?