scalpel@labs: ~/glossary/responsive-images-srcset.mdx5 sections

Responsive Images

A responsive image lists several files in a `srcset` attribute, or across `<picture>` `<source>` elements, each tagged with a width (`w`) or density (`x`) descriptor. The browser picks one to fit the viewport and screen, so the file you actually see depends on the device.

extension: Scalpel Imagesupdated: 2026-08-14read_time: 4 min
less responsive-images-srcset.mdx

Why it matters

A mobile phone is 375 pixels wide, a tablet 768, and a desktop monitor 1440. Serving the same high-resolution 2400px wide JPEG to all three is wasteful. The phone user downloads a file three times larger than necessary. Responsive images solve this: offer several file sizes, and let the browser pick one that matches the viewport and the user's screen density.

The URL in the src attribute is often just a small fallback. The full-resolution file lives only in srcset, so a tool that reads src alone downloads the small version when a large one exists. When you're trying to get the best-quality version of an image, reading srcset is essential.

How it works

The srcset attribute and the w descriptor: <img src="small.jpg" srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w" alt="photo" /> tells the browser: "I have three versions. small.jpg is 400 pixels wide, medium.jpg is 800, and large.jpg is 1600. Pick one." The browser compares the viewport width (say, 768px on a tablet) against these widths and usually picks the smallest candidate that's at least as wide as the viewport. On the tablet, it would pick medium.jpg (800px, which is wide enough for the 768px screen).

The w descriptor is always the natural width of the file in pixels, not the display width on the page. This matters: a 1600px file can display in a 300px box on your page (CSS shrinks it). The w descriptor still says 1600.

The sizes attribute: This is where it gets nuanced. By default, the browser assumes the image displays at 100% of the viewport width. But if your image is half the width of the page (say, in a two-column layout), you should tell the browser that with sizes. <img srcset="small.jpg 400w, large.jpg 1600w" sizes="(max-width: 768px) 100vw, 50vw" alt="photo" /> says: "On screens 768px or narrower, display this image at full viewport width; wider screens display it at 50% of the viewport." Now the browser knows to pick the right candidate. Without sizes, it might pick too small a file.

The x descriptor: Older method for handling screen density. <img srcset="photo.jpg 1x, photo-2x.jpg 2x" alt="photo" /> means "on regular screens, use photo.jpg; on high-density screens (Retina, etc.), use photo-2x.jpg." This is less flexible than w because the browser doesn't know about viewport width or layout, only pixel density. Most new sites use w instead.

The <picture> element: A more powerful alternative to srcset for "art direction": different crops or compositions for different screen sizes. <picture> wraps multiple <source> elements, each with media queries:

<picture>
  <source media="(min-width: 1200px)" srcset="hero-wide.jpg 1200w, hero-wide-2x.jpg 2400w">
  <source media="(min-width: 768px)" srcset="hero-tablet.jpg 768w, hero-tablet-2x.jpg 1536w">
  <source media="(max-width: 767px)" srcset="hero-mobile.jpg 400w, hero-mobile-2x.jpg 800w">
  <img src="hero.jpg" alt="Hero">
</picture>

The browser walks top-to-bottom, finds the first media query that matches, and uses that source's srcset. This lets you serve entirely different compositions for different breakpoints: a close-up crop for mobile, a wide shot for desktop.

Lazy-loaded srcset: Modern sites often pair srcset with loading="lazy" so images below the fold don't load until the user scrolls. But the srcset parsing still happens at page load time, so every candidate URL is known immediately. The actual download waits.

What does not matter

The x descriptor is becoming legacy. You don't need to maintain both w and x descriptors; choose one. The w descriptor is more flexible for modern layouts.

You don't need perfect coverage for every possible screen width. Three or four candidates usually cover well: small (mobile), medium (tablet), large (desktop), and extra-large (ultrawide). More than that, you're guessing at device widths that may not exist.

The order of candidates in srcset doesn't matter. The browser doesn't pick "the first one that matches"; it evaluates all of them and picks the best fit.

Code example

A production-ready pattern:

<!-- Width-based responsive image with sizes -->
<img
  src="photo-400.jpg"
  srcset="
    photo-400.jpg 400w,
    photo-800.jpg 800w,
    photo-1200.jpg 1200w,
    photo-1600.jpg 1600w
  "
  sizes="
    (max-width: 600px) 100vw,
    (max-width: 1200px) 50vw,
    33vw
  "
  alt="Product shot"
  width="1200"
  height="800"
/>

This says: on screens up to 600px, the image fills the full viewport; on screens 600–1200px, it takes 50% of the width; on larger screens, 33%. The browser picks the smallest file that's wide enough.

With <picture> for art direction:

<picture>
  <!-- Desktop: full-width hero -->
  <source
    media="(min-width: 1024px)"
    srcset="hero-wide.jpg 1200w, hero-wide-2x.jpg 2400w"
  />
  <!-- Tablet: slightly cropped -->
  <source
    media="(min-width: 600px)"
    srcset="hero-tablet.jpg 800w, hero-tablet-2x.jpg 1600w"
  />
  <!-- Mobile: tight crop on the subject -->
  <img
    src="hero-mobile.jpg"
    srcset="hero-mobile.jpg 400w, hero-mobile-2x.jpg 800w"
    alt="Hero banner"
    width="400"
    height="400"
  />
</picture>

To parse a srcset attribute in JavaScript:

function parseSrcset(srcsetString) {
  return srcsetString
    .split(',')
    .map(candidate => {
      const [url, descriptor] = candidate.trim().split(/\s+(?=\S*(?:\s|$))/);
      return { url, descriptor };
    });
}

const candidates = parseSrcset(
  'small.jpg 400w, medium.jpg 800w, large.jpg 1600w'
);
console.log(candidates);
// [
//   { url: 'small.jpg', descriptor: '400w' },
//   { url: 'medium.jpg', descriptor: '800w' },
//   { url: 'large.jpg', descriptor: '1600w' }
// ]

How Scalpel shows it

Images sourced from srcset or <picture> carry a SET badge. Scalpel reads every candidate and keeps the largest by default, which is almost always what you want to download. You get the highest-quality version available, not whatever size the browser happened to pick for your current screen.

Filter by source to see only responsive images, or sort by size to find which candidates were the largest. If a srcset has very similar files (say, only 50 pixels different), Scalpel clusters them together so you don't accidentally download multiple versions of the same photo.

Sources