Skip to the content.

Responsive images: describe the options, let the browser choose

Responsive images: describe the options, let the browser choose

Ship one image to every device and you lose both ways: a phone on cellular downloads a 2000px hero it will render at 375px (wasted data and a slow LCP), while a retina laptop gets an image too small to look crisp. The fix is not to pick a “medium” size that is wrong for everyone. It is to describe the options and let the browser choose, because at request time the browser knows things you never can at build time — the device pixel ratio, the viewport width, the current network — and it picks the smallest file that will still look sharp. srcset and sizes are how you hand it that choice.

The author lists image widths; the browser matches one to the device A srcset lists 400w, 800w and 1600w options. Three devices — phone, laptop, retina — each receive the appropriately sized file chosen by the browser. srcset400 · 800 · 1600w phone → 400w laptop → 800w retina → 1600w browser picks thesmallest sharp file
You publish several widths and describe the slot; the browser, knowing the device and viewport, downloads exactly one — the smallest that still looks crisp.

srcset lists the widths; sizes describes the slot

srcset gives the browser the candidate files and their intrinsic widths (the w descriptor). sizes tells it how wide the image will display at various breakpoints, so it can do the maths — display width × pixel ratio — and pick:

<img
  src="/hero-800.jpg"                         <!-- fallback for old browsers -->
  srcset="/hero-400.jpg 400w,
          /hero-800.jpg 800w,
          /hero-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"      <!-- full width on phones, half on desktop -->
  width="800" height="450"                     <!-- reserve the box: no layout shift -->
  alt="A city skyline at dusk">

On a 375px phone the browser computes ~375 CSS px × 2 DPR ≈ 750px and grabs the 800w file; on a wide retina desktop it reaches for the 1600w. You wrote the options once; the browser made the right call for each visitor.

picture: when the image itself should change

srcset chooses between sizes of the same image. When you need to change the image — a different crop on mobile (art direction), or a modern format with a fallback — use <picture> with <source> elements, which let you swap by media query or type:

<picture>
  <source type="image/avif" srcset="/hero.avif">   <!-- modern format if supported -->
  <source media="(max-width: 600px)" srcset="/hero-square.jpg"> <!-- tighter crop on phones -->
  <img src="/hero.jpg" alt="A city skyline at dusk" width="800" height="450">
</picture>

The browser takes the first <source> it supports and matches, falling back to the <img>.

Always reserve the box, and lazy-load below the fold

Two habits make responsive images pay off without side effects. Always set width and height (or an aspect-ratio) so the browser reserves the correct box before the file arrives — otherwise the page reflows when it loads, hurting Cumulative Layout Shift. And add loading="lazy" to images below the fold so they do not compete for bandwidth with the first screen — but never to the LCP hero, which you want eager and high priority. Describe the options, reserve the space, defer what is offscreen, and the browser does the rest better than a fixed choice ever could. The responsive-image-set exercise builds exactly this srcset/sizes markup, which is where the “hand the choice to the browser” idea becomes muscle memory.