Articles

Responsive Images: srcset, sizes, and picture Explained

Responsive images use srcset and sizes to let the browser pick the right file for each screen, cutting wasted bytes without extra JavaScript.

Takina Takina · · 4 min read
Close-up of HTML and CSS code on a screen

Responsive images are images that ship in multiple resolutions so the browser can download only the one it actually needs, instead of forcing every visitor to download a file sized for the largest possible screen. The mechanism is a pair of HTML attributes — srcset and sizes — plus, for trickier cases, the <picture> element. None of it requires JavaScript, and all of it is supported in every current browser.

The problem it solves

A single <img src="hero.jpg"> gives the browser exactly one file, no matter whether it’s rendering on a 320px-wide phone or a 2560px 4K monitor. Serve the phone a desktop-sized image and you’ve wasted bandwidth and delayed paint for no visual benefit — the extra pixels are invisible on a small screen. This directly hurts Core Web Vitals, particularly Largest Contentful Paint, since the hero image is often the LCP element.

The naive fix — serving a fixed “mobile” image via server-side device detection — is fragile and doesn’t account for viewport width, zoom, or pixel density. srcset and sizes let the browser make the call itself, using information only it has: the actual viewport size and device pixel ratio at load time.

How srcset and sizes work together

srcset lists candidate image files with a width descriptor:

<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="A mountain landscape at sunset"
/>

The w descriptor tells the browser each file’s intrinsic width in pixels, not a screen size — it’s a fact about the image, not a media query. sizes is the piece that’s easy to misread: it tells the browser how wide the image will actually render in the layout, as a CSS length, for each condition. Here it says “below 600px viewport width, the image will render at 100% of the viewport; otherwise 50%.”

With both pieces, the browser computes the image’s rendered width, multiplies by the device pixel ratio, and picks the smallest candidate in srcset that’s still large enough. Get sizes wrong — say, hardcoding it to 100vw for an image that’s actually half the viewport in a grid layout — and the browser will happily download a file twice as large as it needs, defeating the whole point.

When you need <picture> instead

srcset/sizes handles resolution switching: same image, different sizes. Sometimes you need art direction: a genuinely different crop or composition depending on viewport, such as swapping a wide landscape shot for a tighter portrait crop on mobile. That’s what <picture> is for:

<picture>
  <source media="(max-width: 600px)" srcset="portrait-crop.jpg" />
  <source media="(min-width: 601px)" srcset="wide-crop.jpg" />
  <img src="wide-crop.jpg" alt="Team photo" />
</picture>

<picture> is also the standard way to serve modern formats with a fallback, letting the browser pick WebP or AVIF when supported and drop back to JPEG otherwise — the <img> inside <picture> is mandatory and acts as the fallback for browsers that don’t match any <source>.

srcset vs picture at a glance

srcset + sizes<picture>
Use caseSame image, different resolutionsDifferent crops or formats per condition
Browser decidesWhich resolution to fetchWhich <source> matches first
Typical triggerViewport width + pixel densityMedia queries or format support
Markup overheadOne <img> tag<source> elements plus a fallback <img>

They’re not mutually exclusive — a <picture> element’s <source> tags can each carry their own srcset, combining art direction with resolution switching in one element.

Loading priority still matters

Serving the right size is only half the job. An <img> correctly sized via srcset that’s still discovered late in the page load won’t help LCP much. Pair responsive images with preload hints for the LCP image, and use loading="lazy" only for images below the fold — see our guide to lazy loading for when eager vs lazy loading is the right call. Setting fetchpriority="high" on a hero image and leaving loading="lazy" off for it are both small levers that matter more than people expect.

If images are served through a CDN with on-the-fly resizing, generating the full set of srcset widths is usually a matter of appending query parameters rather than pre-rendering every size at build time — check what your CDN or image host supports before hand-rolling a build step.

Common mistakes

  • Sizing srcset by device, not by rendered width. The w descriptor is about the file’s pixels, not “for iPhone” or “for desktop.” Pick a spread of widths (e.g., 400, 800, 1200, 1600) and let the math work.
  • Forgetting sizes entirely. Without it, browsers default to 100vw, which is often wrong for images that don’t span the full viewport.
  • Not testing pixel density. A 2x or 3x device pixel ratio changes which candidate the browser picks — verify in DevTools’ network panel with device emulation, not just by resizing the window.
  • Skipping the fallback <img> in <picture>. It’s not optional; it’s what renders when no <source> matches, and it’s also what assistive technology and print stylesheets fall back to.

The takeaway

srcset and sizes let the browser choose the right-sized image for the actual viewport and pixel density, cutting wasted bytes on the page’s heaviest assets without a line of JavaScript. Reach for <picture> when you need a genuinely different image — not just a different size — per condition, such as art-directed crops or format fallbacks. Get the sizes attribute right, since it’s the one piece the browser can’t infer on its own, and pair it with sane loading priority so the size savings actually show up in your Core Web Vitals.

Takina Takina · · 4 min read

What Is the Beacon API? navigator.sendBeacon()

The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.

#Web Development #Frontend #Performance
Takina Takina · · 5 min read

CSS will-change Explained: Compositing and Performance

The CSS will-change property hints the browser to prepare an element for an upcoming change, moving it to its own compositor layer. When to use it and when not to.

#CSS #Web Development #Performance