Picovert

Responsive Images with srcset and sizes: The Complete 2026 Guide

2026-04-117 min read

Serving the right image size to each device is one of the highest-impact performance optimizations you can make. A 3000px hero image on a 375px phone screen wastes bandwidth and slows LCP. The srcset and sizes attributes solve this with native HTML — no JavaScript required.

The problem: one image for all devices

Without responsive images, you typically serve the largest image and let CSS scale it down. This works visually but wastes bandwidth: a 400KB desktop image costs as much on mobile as on desktop, even though a 60KB version would look identical on a small screen.

srcset: defining image variants

The srcset attribute lists available image files and their widths:

<img src="hero-800.webp" srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w" alt="Hero image" />

The browser reads the descriptor (400w = 400 pixels wide) and the current viewport width to select the best file. Without a sizes attribute, it assumes the image will be 100vw wide — usually wrong.

sizes: telling the browser the display width

The sizes attribute describes how wide the image will appear at different viewport breakpoints:

<img srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px" alt="Hero image" />

This tells the browser: on mobile (<640px) the image is full-width; on tablet it's 50% of the viewport; on desktop it's always 800px. The browser combines this with the device pixel ratio to pick the optimal file.

Device pixel ratio

Retina displays have a device pixel ratio (DPR) of 2 or 3, meaning they need 2–3× more pixels to render crisply. A browser on a 375px Retina display with sizes="100vw" will actually request the 800w image (375 × 2 DPR ≈ 750px → nearest variant is 800w). This is handled automatically when you provide enough variants.

The picture element: art direction

For cases where you want to serve a completely different crop on mobile (not just smaller), use <picture>:

<picture> <source media="(max-width: 640px)" srcset="hero-square-400.webp" /> <source media="(min-width: 641px)" srcset="hero-wide-1600.webp" /> <img src="hero-wide-1600.webp" alt="Hero image" /> </picture>

Modern format delivery with picture

Use <picture> to serve AVIF to browsers that support it, with WebP and JPEG as fallbacks:

<picture> <source type="image/avif" srcset="hero.avif" /> <source type="image/webp" srcset="hero.webp" /> <img src="hero.jpg" alt="Hero image" /> </picture>

Combine with srcset on each source for the full responsive + format solution.

Next.js: automatic srcset generation

Next.js's <Image> component generates srcset automatically based on the deviceSizes and imageSizes config. The sizes prop maps directly to the HTML sizes attribute:

<Image src="/hero.jpg" width={1600} height={900} sizes="(max-width: 640px) 100vw, 800px" alt="Hero" />

Without the sizes prop, Next.js defaults to 100vw — set it correctly to avoid loading unnecessarily large images. See our next/image guide for the full trade-off analysis.

Generating variants at scale

For static sites, pre-generate variants at build time using Sharp:

sharp('hero.jpg').resize(400).webp().toFile('hero-400.webp')

For user-uploaded content, use an image CDN that generates variants on the fly. See our image CDN comparison.

For manual one-off conversions, use Picovert's PNG to WebP converter.

Quick checklist

  • Always set sizes — don't rely on the 100vw default
  • Provide at least 3 width variants per image (small, medium, large)
  • Include 2× variants for Retina support
  • Lazy-load below-fold images with loading="lazy"
  • Preload the LCP image with <link rel='preload'>