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