Skip to main content

Responsive images: picture, srcset and sizes

Responsive images let the browser pick the right image file for each device automatically, based on viewport width, pixel density, and format support.

Theory

TL;DR

  • srcset + sizes: list available files with their widths, tell the browser how wide the image displays, and the browser does the rest.
  • picture: you write explicit media queries, the browser follows them exactly. You control which image loads per breakpoint.
  • Same image, different sizes → srcset. Different crops or formats per device → picture.
  • A 375px phone with a 2x display needs a 750px image, not a 375px one. The browser calculates this from sizes.
  • Analogy: srcset is a menu with portion sizes where the server picks based on your needs. picture is when you point at exactly what you want.

Quick example

html
<!-- Browser picks the best image based on viewport + pixel density --> <img src="photo-small.jpg" srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" alt="Product photo" > <!-- 375px phone (1x): loads photo-small.jpg (100vw = 375px, 400w covers it) --> <!-- 375px phone (2x): loads photo-medium.jpg (375 x 2 = 750px needed, 800w fits) --> <!-- 1920px desktop: loads photo-large.jpg (33vw = 640px, 1600w covers 2x) -->

The src attribute is the fallback for old browsers that ignore srcset entirely.

Key difference

srcset with width descriptors tells the browser which files exist and how wide each one is. sizes tells it how wide the image actually renders on screen. The browser combines both to pick the right download. picture works differently: it evaluates <source> elements against explicit media queries and loads the first match, with srcset inside each source handling resolution variants. Use srcset when the image scales. Use picture when the image itself changes.

When to use

  • srcset + sizes: product photos, blog images, hero images that scale proportionally across devices
  • picture: hero images that need different crops on mobile vs desktop, format switching (WebP with JPEG fallback), images where the subject changes per breakpoint
  • Both together: picture handles which crop to show, srcset inside each <source> handles which resolution of that crop to load
  • Single src: SVGs, icons, small decorative images that don't vary by device

Comparison table

Aspectsrcset + sizespicture elementSingle src
Use caseSame image, different sizesDifferent images per breakpointStatic, device-agnostic
Who decides?Browser (viewport + DPR)You (via media queries)No choice
Bandwidth savingsHighHighNone
Art directionNoYesNo
Format switchingNoYesNo
Best forScaling photos, galleriesDifferent crops, WebP/JPEG fallbackIcons, SVG

How the browser selects an image

When the browser hits srcset + sizes, it reads the sizes attribute to get the display width in CSS pixels, multiplies by the device pixel ratio, then picks the smallest image in srcset that covers that pixel count without blurring. For picture, it goes through <source> elements top to bottom, stops at the first matching media and type, then runs the same algorithm on any srcset inside that source. The browser caches this decision and won't re-request unless the viewport changes significantly.

One thing that surprises people: the browser can factor in cached images and network conditions. If a larger file is already in cache, it may serve that instead of fetching the theoretically better match.

Common mistakes

Mistake 1: Leaving out sizes

html
<!-- Wrong: browser defaults to 100vw and may load an oversized file --> <img src="photo.jpg" srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w" alt="Photo" /> <!-- Right: tell the browser the actual display width --> <img src="photo.jpg" srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" alt="Photo" />

Without sizes, a thumbnail on a 1440px desktop triggers a download of the full-width image. The bandwidth cost is real.

Mistake 2: Mixing width and pixel density descriptors

html
<!-- Wrong: browser ignores the entire srcset --> <img src="logo.png" srcset="logo.png 1x, logo-2x.png 2x, logo-wide.png 800w" alt="Logo" /> <!-- Right: use one descriptor type, not both --> <img src="logo.png" srcset="logo.png 1x, logo-2x.png 2x" alt="Logo" />

Width descriptors need sizes to work. Pixel density descriptors are absolute. Mixing them breaks the selection algorithm and the browser falls back to src.

Mistake 3: sizes doesn't match your CSS

html
<!-- Wrong: CSS caps the image at 600px, but sizes says 100vw --> <style>img { width: 100%; max-width: 600px; }</style> <img src="photo.jpg" srcset="photo-400.jpg 400w, photo-1200.jpg 1200w" sizes="100vw" alt="Photo" /> <!-- On a 1440px desktop: browser downloads photo-1200.jpg --> <!-- but CSS renders it at 600px. Bandwidth wasted. --> <!-- Right: sizes reflects the CSS constraint --> <img src="photo.jpg" srcset="photo-400.jpg 400w, photo-1200.jpg 1200w" sizes="(max-width: 640px) 100vw, 600px" alt="Photo" />

Mistake 4: Using picture for simple scaling

html
<!-- Overcomplicated for what srcset handles better --> <picture> <source media="(max-width: 640px)" srcset="photo-small.jpg" /> <source media="(max-width: 1024px)" srcset="photo-medium.jpg" /> <img src="photo-large.jpg" alt="Photo" /> </picture> <!-- Simpler, and adapts to future screen sizes automatically --> <img src="photo-small.jpg" srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" alt="Photo" />

Hardcoded media queries in picture break on devices outside your expected breakpoints. srcset lets the browser adapt to any screen automatically.

Mistake 5: Expecting the smallest image on a small phone

A 375px phone with a 2x display loads the 800w image, not the 400w one. The math: 375 x 2 = 750px needed, so the browser picks 800w as the smallest option that won't degrade quality. This is correct behavior. Account for DPR when deciding which image sizes to generate.

Real-world usage

  • Next.js <Image> component: generates srcset automatically, you only set sizes and dimensions
  • Shopify product images: picture + srcset for mobile crop and WebP switching
  • WordPress with Smush and similar plugins: automatically generates srcset for all uploaded images
  • Gatsby gatsby-plugin-image: generates srcset with blurred placeholders
  • Cloudinary and Imgix: CDN services that generate srcset URLs on demand, no manual file creation needed
  • CSS backgrounds: image-set() for the same idea in CSS (background-image: image-set(url(photo.jpg) 1x, url(photo-2x.jpg) 2x))

Follow-up questions

Q: Why can't you mix width descriptors (400w) and pixel density descriptors (2x) in the same srcset?
A: They use different selection algorithms. Width descriptors need sizes to calculate display width first. Pixel density descriptors are absolute and skip that step. Mixing them creates ambiguity the browser won't resolve, so it drops the entire srcset and loads src.

Q: If I use picture with media queries, do I still need srcset inside <source> elements?
A: Yes. picture handles art direction (which version of the image). srcset inside each <source> handles resolution variants of that version. On a 2x phone, picture picks the mobile source, then srcset picks the 2x variant of that mobile image.

Q: A phone is loading a larger image than expected. What do you check first?
A: Device pixel ratio. Most modern phones are 2x or 3x, so the browser requests 750 to 1125px worth of image. Open Chrome DevTools, go to Network, filter by Img, and check which srcset option actually loaded.

Q: What happens if no <source> in <picture> matches?
A: The browser falls through to the <img> inside <picture>, which acts as the required fallback. That is why <img> is mandatory inside <picture>, not optional.

Q: (Senior level) A designer gives you a 3000x2000px image for a responsive page. Walk through your decision process.
A: First question: does the image content change per device, or just scale? Same subject means srcset + sizes. Mobile needs a portrait crop means picture for art direction. Then calculate actual display widths: mobile 100vw (~375px), tablet 50vw (~512px), desktop 33vw (~640px). Generate at 1x and 2x: six files total. Automate with a CDN or build tool rather than manual exports. Verify in DevTools that each device loads the right file.

Examples

Basic srcset for a product grid

html
<!-- Image fills full width on mobile, half on tablet, a third on desktop --> <img src="product-800.jpg" srcset=" product-400.jpg 400w, product-800.jpg 800w, product-1600.jpg 1600w " sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" alt="Blue running shoes" > <!-- 375px phone (1x) → product-400.jpg (375px needed) --> <!-- 375px phone (2x) → product-800.jpg (750px needed) --> <!-- 1440px desktop (1x) → product-800.jpg (1440 x 0.33 = ~475px) --> <!-- 1440px desktop (2x) → product-1600.jpg (~950px needed) -->

The sizes values must match your CSS layout. If the grid changes from three columns to four, update sizes to match.

picture for art direction with WebP fallback

html
<!-- Hero: portrait crop on mobile, landscape on desktop, WebP where supported --> <picture> <!-- Mobile portrait crop, WebP --> <source media="(max-width: 640px)" type="image/webp" srcset="hero-mobile.webp 375w, hero-mobile@2x.webp 750w" /> <!-- Mobile portrait crop, JPEG fallback --> <source media="(max-width: 640px)" srcset="hero-mobile.jpg 375w, hero-mobile@2x.jpg 750w" /> <!-- Desktop landscape, WebP --> <source type="image/webp" srcset="hero-desktop.webp 1200w, hero-desktop@2x.webp 2400w" /> <!-- Fallback for browsers that don't support picture --> <img src="hero-desktop.jpg" alt="Team working in the office" /> </picture> <!-- Chrome on mobile → hero-mobile.webp (or @2x variant) --> <!-- Safari on mobile → hero-mobile.jpg --> <!-- IE11 → hero-desktop.jpg (ignores picture) -->

The browser reads <source> elements in order. The first one where both media and type match wins.

Format switching without changing the crop

html
<!-- Same image, just serve WebP to browsers that support it --> <picture> <source type="image/webp" srcset="photo.webp 400w, photo-2x.webp 800w, photo-3x.webp 1200w" sizes="(max-width: 640px) 100vw, 50vw" /> <img src="photo.jpg" srcset="photo.jpg 400w, photo-2x.jpg 800w, photo-3x.jpg 1200w" sizes="(max-width: 640px) 100vw, 50vw" alt="Landscape photo" /> </picture> <!-- Chrome, Firefox, modern Edge → WebP (typically 25-35% smaller than JPEG) --> <!-- Safari 13 and older → JPEG via img srcset --> <!-- IE11 → photo.jpg from img src only -->

This is the standard pattern for serving modern formats with a safe fallback. No server-side logic needed.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?