⚡ Web Optimization

Responsive Images: srcset and sizes Explained

⏱ 7 min read 📅 Updated March 2026

A photo displayed at 400 px on mobile does not need to be 1600 px wide. Yet without responsive image markup, every user downloads the same large file. The srcset and sizes attributes solve this: they tell the browser what image candidates exist and how large each will display, letting it choose the optimal file to download.

The problem: one image for all screens

Without responsive images, you face a choice: serve a large file (fast on desktop, slow on mobile) or a small file (fast on mobile, blurry on Retina desktop). Neither is optimal. Responsive image markup lets you serve both, and let the browser pick.

srcset with width descriptors (w)

The most common pattern. Provide multiple image widths and the browser picks based on screen width and pixel density:

<img
  srcset="photo-400.webp 400w,
          photo-800.webp 800w,
          photo-1200.webp 1200w,
          photo-1600.webp 1600w"
  src="photo-800.webp"
  alt="Description"
  width="800" height="600"
>

The w descriptor is the actual pixel width of that image file, not a CSS breakpoint. The browser uses this along with screen width and device pixel ratio to pick the most efficient option.

The sizes attribute

Without sizes, the browser assumes the image is 100% of the viewport width. For a sidebar image displayed at 300 px on desktop, this causes the browser to download a much larger file than needed. sizes tells the browser how wide the image will be displayed at each breakpoint:

<img
  srcset="photo-300.webp 300w,
          photo-600.webp 600w,
          photo-900.webp 900w"
  sizes="(max-width: 768px) 100vw,
         (max-width: 1200px) 50vw,
         300px"
  src="photo-600.webp"
  alt="Description"
  width="600" height="400"
>

Read sizes top to bottom: on screens narrower than 768 px, the image is 100% of the viewport. On screens up to 1200 px, it is 50% of the viewport. On all other (larger) screens, it is exactly 300 px wide.

srcset with pixel density descriptors (x)

For fixed-size images (logos, avatars, icons), use pixel density descriptors instead:

<img
  srcset="logo.webp 1x, [email protected] 2x, [email protected] 3x"
  src="logo.webp"
  alt="Company logo"
  width="200" height="60"
>

The browser downloads the 2x version on Retina displays, 3x on very high-DPI screens, and 1x everywhere else.

Combining with <picture> for format negotiation

Combine <picture> for format switching with srcset for responsive sizing:

<picture>
  <source
    type="image/avif"
    srcset="photo-400.avif 400w, photo-800.avif 800w, photo-1200.avif 1200w"
    sizes="(max-width: 768px) 100vw, 800px"
  >
  <source
    type="image/webp"
    srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
    sizes="(max-width: 768px) 100vw, 800px"
  >
  <img
    src="photo-800.jpg"
    srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
    sizes="(max-width: 768px) 100vw, 800px"
    alt="Description"
    width="800" height="600"
    loading="lazy"
  >
</picture>

Which image sizes to generate?

A practical starting set for most content images:

  • 400 px: mobile (small screens, 1x)
  • 800 px: mobile Retina (2x), small tablet
  • 1200 px: desktop, tablet Retina
  • 1600 px: large desktop, wide images

For hero/full-width images, add a 2400 px variant for large Retina displays.

Automation: Generating all these variants manually is impractical. Use an image CDN (Cloudinary, Imgix, Cloudflare Images) to generate sizes on demand via URL parameters, or a build tool plugin (next/image, gatsby-image, astro:assets) that handles it automatically.
Key takeaway: Use srcset with w descriptors for content images, and add a sizes attribute that matches your CSS layout. This single change can halve the image bytes downloaded by mobile users. Combine with <picture> for format negotiation to deliver AVIF to modern browsers and JPEG as a fallback.

Try imgpact tools

Free browser-based image tools, no upload, no signup.