The problem: one image for all devices

A single <img src="..."> serves everyone the same image — the 4K desktop and the old phone alike. Either it's too large for the phone (wasted bandwidth, slow load) or too small for the desktop (blurry). Responsive images solve this: the browser is offered several variants and picks the right one itself.

srcset: offering the choice

With srcset you list image variants along with their real pixel width (the w stands for width in pixels):

<img
  src="photo-800.jpg"
  srcset="photo-480.jpg 480w,
          photo-800.jpg 800w,
          photo-1200.jpg 1200w,
          photo-1600.jpg 1600w"
  alt="Description">

The src remains as a fallback for very old browsers. Everyone else reads srcset and now knows: this image exists in four widths. What's still missing is how wide the image appears in the layout — that's what sizes is for.

sizes: telling the browser the layout width

Without sizes, the browser assumes the image fills the full viewport width. If that's not true (say, in a two-column layout), it loads too large. sizes describes the display width per screen size:

<img
  src="photo-800.jpg"
  srcset="photo-480.jpg 480w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1000px) 50vw,
         800px"
  alt="Description">

Read it as: "up to 600px viewport width the image fills 100% of the width; up to 1000px it fills half; otherwise it's 800px wide." From that width plus the device's pixel density, the browser computes which srcset variant fits best — and loads only that one.

Why this also serves Retina displays

The clever part: a Retina phone with 400 CSS pixels of width and double pixel density needs an 800px-wide image for a crisp result. The browser works exactly that out from sizes and device density — you don't have to manage @2x variants yourself.

Creating the variants

For each srcset width you need a real image file. You produce them from the original by scaling it down to the respective widths — with the resize tool (480, 800, 1200, 1600 px) and then compressing. Practical rule of thumb for the widths:

  • 480w — smartphones
  • 800w — tablets, small layouts
  • 1200w — desktop
  • 1600w — large/Retina displays

Take format selection along too: the picture element

If you also want to serve modern formats (WebP, AVIF) with a fallback, combine srcset with the <picture> element:

<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Description" width="800" height="600">
</picture>

The browser takes the first format it understands. Modern browsers get small AVIF/WebP files, old ones get the JPG fallback — all without JavaScript.