Responsive Images — srcset, sizes, <picture>
Serve the right image for each device with native HTML — no JS. srcset + sizes lets the browser pick by resolution/viewport (resolution switching); <picture> with <source> handles art direction (different crops) and format fallback (AVIF/WebP → JPEG). Add width/height (or aspect-ratio) to prevent layout shift (CLS), loading="lazy" for below-the-fold, and fetchpriority="high" for the LCP hero. These are the cheapest Core Web Vitals wins — see Core Web Vitals.
srcset with width descriptors + sizes — how does it work?
You list candidate images with their intrinsic widths (w), and tell the browser how wide the image will render at each breakpoint via sizes. The browser picks the smallest file that’s still sharp for the device’s viewport and DPR:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
width="800" height="600" alt="…" />“On screens ≤600px the image is full width; otherwise half.” The browser does the math (CSS px × DPR) and chooses. src is the fallback for old browsers.
Width descriptors (w) vs density descriptors (x)?
w+sizes— for images whose rendered size varies with the layout (responsive). Most cases.x— for a fixed display size, just swapping for DPR (1×/2×/3×):srcset="logo.png 1x, logo@2x.png 2x". Nosizesneeded.
Use w for fluid images, x for fixed-size assets like logos/icons.
When do you use <picture> instead of srcset?
Two cases srcset alone can’t handle:
- Art direction — a different crop/image per breakpoint (wide banner on desktop, square on mobile), not just a smaller version.
- Format negotiation with fallback — offer AVIF/WebP and let the browser fall back to JPEG.
<picture>
<source type="image/avif" srcset="hero.avif" />
<source type="image/webp" srcset="hero.webp" />
<source media="(max-width: 600px)" srcset="hero-square.jpg" />
<img src="hero.jpg" width="1200" height="630" alt="…" />
</picture>The browser uses the first matching <source>; <img> is the required fallback and carries alt.
How do these attributes affect Core Web Vitals?
width/height(or CSSaspect-ratio) reserve space → no layout shift → better CLS.loading="lazy"defers offscreen images → less bandwidth, faster initial load. Never lazy-load the LCP image.fetchpriority="high"on the hero tells the browser to load it first → better LCP.fetchpriority="low"for de-prioritizing.decoding="async"avoids blocking rendering on image decode.- Smaller, modern formats (AVIF/WebP) cut bytes → faster LCP.
What’s the difference between loading="lazy" and a preload?
Opposite intents. loading="lazy" delays offscreen images. <link rel="preload" as="image" fetchpriority="high"> promotes a critical image (the LCP) so the preload scanner fetches it early — useful when the image is set via CSS or discovered late. See Frontend Performance — Senior Interview Prep.
Gotchas
- Lazy-loading the LCP image hurts LCP — the single most common image perf mistake. Hero = eager +
fetchpriority="high". - Missing
sizeswithwdescriptors defaults to100vw, so the browser over-downloads on multi-column layouts. - No
width/height→ the image pops in and shifts content (CLS). Always set dimensions oraspect-ratio. <picture>altgoes on the<img>, not on<source>; omitting the<img>breaks everything.- CDN/
<img>srcset explosion — generating dozens of sizes is wasteful; pick a sensible ladder (e.g., 400/800/1200/1600) matched to breakpoints. Image CDNs (Cloudinary, Next<Image>) automate this. - Empty
alt=""is correct for decorative images (skip them in AT); meaningful images need descriptive alt.
Related
Further reading
- MDN — Responsive images: https://developer.mozilla.org/en-US/docs/Web/HTML/Responsive_images
- MDN —
<picture>: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture - web.dev —
fetchpriority: https://web.dev/articles/fetch-priority
Interview angle 5
- “How do you serve the right image to each device?” -
srcsetwithwdescriptors plussizesfor resolution switching,xdescriptors when the rendered size is fixed and only DPR varies, and<picture>when you need art direction or an AVIF/WebP fallback chain.srcsetlets the browser choose;<picture>is you choosing.- Follow-up: “Why does
sizesmatter ifsrcsetalready lists widths?” - because the browser picks before layout exists. Withoutsizesit assumes the image is viewport-width and downloads something far too large.
- Follow-up: “Why does
- “How do images cause layout shift, and how do you stop it?” - the box has
no dimensions until the bytes arrive, so everything below reflows. Set
widthandheightattributes, oraspect-ratioin CSS, and the browser reserves the box before the download starts. - “When is lazy loading wrong?” - on the LCP image.
loading="lazy"defers the exact resource the metric is timing, so it makes the number worse. Lazy everything below the fold and nothing above it. - “What is the cheapest LCP win on an image-heavy page?” -
fetchpriority="high"on the hero, which tells the browser to spend its connection budget there first.decoding="async"on the rest keeps decode work off the critical path. - “Who should generate the size ladder?” - not you, by hand. An image CDN or a framework loader emits the formats and widths on demand, which is what keeps the ladder from rotting the first time the design changes.