PixSqueeze

Fast · Private · High-Efficiency
Formats

Responsive images: srcset and sizes

Serving one image to every device means either phones download desktop-sized files or desktops get something blurry. srcset solves this, and the sizes attribute is the half everyone gets wrong.

The same web page shown adapted to a phone, a tablet and a desktop screen
Photo: Muhammad Rafizeldi / Wikimedia Commons, CC BY-SA 3.0

The problem in one sentence

A 400-pixel-wide phone and a 2560-pixel-wide monitor should not download the same file, and no single file is correct for both1.

Pick the large one and phones waste most of the bytes they download — pixel count scales with area, so a 2560px image has roughly 41 times the pixels of a 400px one. Pick the small one and it looks soft everywhere else.

srcset: offering the browser a menu

srcset lists the files you have and how wide each one is, using w descriptors1:

<img src="photo-800.webp" srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1600.webp 1600w" …>

The w value is the real pixel width of that file. It is not a guess or a label — get it wrong and the browser's arithmetic is wrong.

src stays as a fallback for anything that does not understand srcset.

sizes: the half that is usually wrong

srcset alone is not enough. The browser needs to pick a file before it has done layout, so it cannot know how wide your image will actually be displayed. sizes is you telling it, in advance1.

sizes="(max-width: 700px) 100vw, 700px" means: below a 700px viewport this image fills the width; above it, it is 700px wide.

Omit sizes and the browser assumes 100vw — the full viewport width. On a desktop with a narrow content column, that makes it download something far larger than needed. This single default is behind a great deal of "I added srcset and nothing improved"2.

Which sizes to actually generate

You do not need a file for every width. Three or four covers nearly everything: roughly 400, 800, 1600, and 2400 for a full-bleed hero.

Keep every variant at the same aspect ratio. Mixed ratios mean the reserved layout box is wrong for some selections, which reintroduces layout shift3 — see the CLS guide.

Compress each variant properly. srcset chooses which file to fetch; it does nothing about how big that file is. Format choice still matters too — WebP vs AVIF vs JPEG.

When to reach for picture instead

<picture> is for art direction — genuinely different crops at different breakpoints, like a wide banner that becomes a square on mobile4.

If you are serving the same image at different resolutions, that is srcset. If you are serving a different image, that is <picture>. Using <picture> for resolution switching works but is more markup for no benefit.

Choosing which widths to generate

Generating a derivative for every conceivable width wastes storage and build time; generating two leaves obvious gaps. A workable approach is to space widths so each step is a meaningful size increase — roughly 1.5× apart — rather than at arbitrary round numbers.

For a typical content image, four widths covers nearly everything: something around 400px for small phones, 800px, 1200px, and 1600px for large or high-density displays. Add a 2000px step only if you genuinely serve full-bleed imagery.

Set the ceiling from what the layout can actually use, not from what the original happens to be. There is no benefit to a 3000px derivative for an image that renders at most 800 CSS pixels — working out that display width is the first step.

The sizes attribute is where this usually breaks

srcset lists what is available; sizes tells the browser how wide the image will render. Get sizes wrong and the browser confidently picks the wrong file — usually a much larger one — and the whole exercise backfires.

The trap is that the browser must choose before CSS layout is complete, so it cannot measure the element. It can only trust what sizes claims. A default of 100vw left on an image that actually renders in a 700px column tells the browser to fetch something sized for the full viewport.

Check it: load a page on a wide screen, then in DevTools compare the image's intrinsic size against its rendered size. A large gap means sizes is lying, and it is worth auditing every template that renders images.

References

  1. Responsive imagesMDN Web Docs↑
  2. Serve responsive imagesweb.dev↑
  3. Cumulative Layout Shift (CLS)web.dev↑
  4. The Picture elementMDN Web Docs↑