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?