HAMZA KAREEM / CONCEPTS hamzawi.dev

Lesson 1072 min read

srcset vs picture (Responsive Images)

Who Actually Picks The Image

Web developmentweb development / performance

One-line rule

img + srcset/sizes = you let the BROWSER choose.

picture + source = YOU choose.

Side by side:

  • Who picks the file — srcset: the browser | picture: you
  • What varies — srcset: same image, different widths | picture: different crops or formats
  • Can the browser overrule you — srcset: yes (DPR, user prefs) | picture: no, first matching source wins
  • Reach for it when — srcset: responsive sizing | picture: art direction, AVIF/WebP fallback

Resolution switching (the 90% case)

<img

src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(min-width: 900px) 50vw, 100vw"
alt="">

sizes is NOT styling. It's a promise: "at >=900px this img renders at 50% of viewport width."

Browser math: 50vw of 1440 = 720 CSS px x DPR 2 = 1440 device px -> picks hero-1600.jpg.

Art direction / format fallback

<picture>

<source type="image/avif" srcset="hero.avif">
<source media="(max-width: 600px)" srcset="hero-square.jpg">
<img src="hero.jpg" alt="">

</picture>

First source whose type AND media match wins, top to bottom. So order AVIF -> WebP -> JPEG, never the reverse.

The gotcha that bites everyone

sizes defaults to 100vw. Forget it on a sidebar thumbnail that renders 300px wide on a 1440px DPR-2 screen, and the browser shops for ~2880px: it downloads hero-1600.jpg (420 KB) instead of hero-400.jpg (38 KB). Identical pixels on screen, 11x the bytes.

And CSS cannot save you — the preload scanner fires that request before your stylesheet is even parsed. That's why the width lives in the HTML attribute.

Two smaller traps:

  • media= on <img srcset> is ignored. media belongs on <source>.
  • Keep width/height on the <img>, even inside <picture> — that's what gives the aspect ratio and prevents CLS. The <source> elements don't do it for you.

Check your work in 1 line

document.querySelectorAll('img').forEach(i =>

console.log(i.currentSrc, i.clientWidth, devicePixelRatio))

currentSrc is what the browser actually fetched. If its intrinsic width is far above clientWidth x devicePixelRatio, your sizes attribute is lying to the browser.

Your exercise

Open any page with a non-full-width image. Run the snippet above, then compute clientWidth x devicePixelRatio and compare it to the real width of the file it chose. If it over-fetched, write the correct sizes for that layout, reload, and watch which candidate it switches to.

Bonus question: you have one photo that needs a square crop on mobile AND an AVIF version. Which tool do you need — and can you use both at once?