Performance: The Main Payoff

If your primary objective is cutting page weight and improving load times, responsive images are one of the most effective levers you can pull. The core idea is simple: let the browser download only the image data it actually needs for the current viewport and display. When a browser can pick between a 300×300 and a 600×600 source, choosing the smaller one on a small screen can save up to 4× in bytes over the wire. Real-world case studies on small viewports have measured byte savings between 70–90%, and overall page performance correlates strongly with total image weight.

Art Direction: When You Need More Control

Responsive images aren't only about serving smaller copies of the same file. Sometimes the layout calls for a genuinely different crop or composition at different screen sizes—this is art direction. The <picture> element exists precisely for those cases, letting you pair media queries with sources so the browser loads the most appropriate variant. The element also covers fallback image formats and use cases like serving different images for dark mode, giving you explicit control over what renders.

With the goals laid out, here is what the two syntaxes cover and how the related attributes and values fit together.

Using srcset for Resolution Switching

The <img src="" alt=""> syntax is built for serving differently-sized versions of the same image. While you could technically point it at entirely different files, browsers assume every entry in a srcset is visually identical. They will pick whichever they consider optimal in ways that are hard to predict, so don't use this syntax for art direction.

The lightest-touch responsive images technique is adding a srcset with x descriptors to label sources by pixel density.

<img 
  alt="A baby smiling with a yellow headband."
  src="baby-lowres.jpg"
 
>

Here the src points to the default 1× copy, so the smallest or slowest resource is the fallback. A second entry with a 2x descriptor tells the browser a higher-resolution copy exists for displays that can use it. You can add as many pixel-density variants as you like, and the browser will pick the right one.

Demo

The limitation of x descriptors is that they only react to one variable: display pixel-density. On responsive layouts, images also change size with the viewport, so the browser must account for two factors—screen sharpness and the image's rendered layout width. That's where w descriptors and the sizes attribute come in.

Letting the browser choose: srcset with width descriptors

Pairing srcset with w descriptors is the dominant responsive-image pattern on the web, used in roughly 85% of cases. The mechanics are familiar — you still offer the same image in multiple sizes — but the labels change. Instead of tagging each file with a pixel-density value (x), you tag it with its intrinsic width: 300w for a 300 pixel-wide baby-s.jpg, and so on.

That shift unlocks layout-aware selection, but only if you provide the sizes attribute. The sizes attribute tells the browser how much horizontal space the image will actually occupy in the layout, so it can compute an ideal resource width before it has parsed your CSS. Without it, the browser has no way to make an informed choice.

<img 
  alt="A baby smiling with a yellow headband."
 
 
>
Demo

Writing sizes that matches reality

The tricky part of sizes is that it's tightly coupled to your stylesheet. An image's render width depends on your specific layout grid, spacing, and breakpoints, not just viewport size. Take a common three-breakpoint layout where an image sits alongside a fixed-width sidebar at the largest size, drops below the sidebar at the medium size, and loses body margins at the smallest size.

Accounting for every gap, margin, and padding by hand is error-prone. In a layout where the sidebar uses a 200px column, a correct value might be calc(100vw - 9rem - 200px) at the widest breakpoint. At the medium size, with the sidebar out of the way, calc(100vw - 6rem) works. At the smallest, calc(100vw - 2rem) covers the remaining padding.

Even careful hand-crafted math like this often fails in the details. Martin Auswöger's RespImageLint tool resizes the page programmatically across many viewport widths and derives the actual observed image width, producing a precise sizes attribute that is impractical to maintain by hand:

<img
  ...
 
>

If a pixel-perfect sizes value is a requirement, the pragmatic path is to write a rough estimate first, then run a tool like RespImageLint and copy its output. For a deeper treatment of how w descriptors and sizes work under the hood, Eric Portis has a detailed write-up.

The "close enough" approach

Perfect accuracy is not always necessary. A looser sizes like 96vw states the image will be nearly full-width but never quite there. A value like (min-width: 1000px) 33vw, 96vw covers a three-column desktop layout with a near-full-width mobile fallback. Some automated systems default to something like (max-width: 1000px) 100vw, 1000px — a safe guess that assumes worst-case full-bleed rendering and caps the target at 1000px.

However you generate the values, there’s a strong case for centralizing them. Since layouts change over time, hardcoding sizes across dozens of markup templates guarantees drift. Any templating layer — PHP constants, Rails config variables, React context, or Liquid variables in a static site — lets you define the value in one place and inject it everywhere.

<?php
  // Somewhere global
  $my_sizes = "";
?>

<img
 
  src=""
  alt=""
 
/>

The browser's selection logic

With a sizes value in place, the browser computes a target image width by factoring the rendered size and the user's pixel density. On a 1200px viewport with a 2x display and an image at 40vw, the ideal source is roughly 960 pixels wide; the browser then picks the closest option from srcset.

The spec intentionally leaves room for additional heuristics. A browser may also weigh network conditions or a user's data-saver preference, and some will pull a heavier already-cached image rather than fetch a fresh smaller one. Because selection is ultimately the browser's call, every resource in a srcset must be the same image at a different size — you can't assume which one will win.

This raises a fair question: why hand the browser information it will learn anyway? The answer is speed. By the time the browser has parsed your HTML and CSS to compute layout, it has already stalled image loading. The sizes attribute lets it start downloading the right variant the moment it sees the <img> tag. Lazy-loaded images are an exception — layout has completed by then, which is why the lazysizes library writes sizes attributes automatically at load time, and native auto-sizes is under discussion for the spec.

<img
 
 
  class="lazyload" 
/>

A final sizes trick: it can exceed the viewport. For a click-to-zoom effect, instead of swapping src in JavaScript, you can update sizes to something like 200vw. If a high-resolution source already exists in srcset, the browser will fetch it automatically.

Enforcing design choices with <picture>

Where srcset/sizes delegates the final decision to the browser, <picture> makes the browser follow your rules. That distinction matters when you need more than a resolution swap.

Art direction is the classic use case: showing a zoomed-out landscape shot on wide screens, a tighter crop on tablets, and a close-up on phones. Because the element forces the browser to respect media queries, you can guarantee that nobody on a small screen lands on a distant view that loses its impact.

<picture>
  <source 
   
    media="(min-width: 1000px)"
  />
  <source 
   
    media="(min-width: 600px)"
  />
  <img 
    src="baby-zoomed-in.jpg" 
    alt="Baby Sleeping"
  />
</picture>

Art direction goes far beyond crops. The same mechanism can send dark-mode-tinted images to users with that preference, suppress animated GIFs for people with a “prefers reduced motion” setting, rearrange content to fit short viewports, cap image fidelity on ultra-high-resolution devices, or serve monochrome high-res images to printers and e-ink displays.

The <source> element accepts srcset, so you can combine explicit breakpoints with density-based choices — at a cost in verbosity. Each visual variant needs its own resized set, and the markup grows quickly.

<picture>
  <source 
   
    media="(min-width: 1000px)"
  />
  <source 
   
    media="(min-width: 600px)"
  />
  <img 
   
    src="baby-zoomed-out.jpg"
    alt="Baby Sleeping"
  />
</picture>

Format fallbacks and next-gen codecs

The <picture> element also solves format support. You can offer WebP first with a JPEG fallback:

<picture>
  <source>
  <img src="party.jpg" alt="A huge party with cakes.">
</picture>

Browsers that support WebP use it; Safari ignores it and pulls the JPEG. In one photo example, that WebP file came in at roughly 10% of the JPEG's size.

Creating WebP assets is less friction than it used to be — online converters and command-line tools exist, and some design apps export it directly. For multi-format delivery, a common pattern layers JPEG-XR for IE11, JPEG 2000 for Safari, and WebP everywhere else, all inside <picture>. IE11 doesn't support the element itself, which works in your favor: it ignores everything and falls through to the <img> fallback with a format it understands.

<picture>
  <source type="image/webp" />
  <source type="image/jp2" />
  <img src="https://css-tricks.com/images/cereal-box.jxr" type="image/vnd.ms-photo" />
</picture>

Producing the variants

You can generate resized copies manually, but it's tedious. More realistic options are automating it in a build step, or delegating to an image hosting service that transforms images via URL parameters. Nearly every image CDN — Cloudinary, Netlify, imgix, Image Optim, Filestack, Cloudflare — offers on-the-fly resizing, often alongside cropping, filtering, and automatic next-gen format negotiation.

Design tools are catching up too. Figma's export panel, for instance, lets you export a single selection at multiple sizes and remembers the previous export settings.

The Mac Preview app resizing an image, which is something that literally any image editing application (including Photoshop, Affinity Designer, Acorn, etc.) can also do. Plus, they often help by exporting the variations all at once.

Automating away the syntax

Hand-authoring responsive-image markup is fragile enough that automation is the recommended route. Tooling already exists across the stack:

  • Cloudinary's responsive breakpoints generator, including an API for computing ideal sizes.
  • WordPress, which generates multiple image versions and emits responsive syntax by default.
  • Gatsby's gatsby-image plugin, plus React component abstractions that handle the heavy lifting.
  • Images Responsiver, a Node module with an Eleventy plugin that pairs well with CDN-based resizing.

The broader point is to find an abstraction layer you trust — and to understand the syntax well enough to verify that your abstraction is doing the job correctly.

Here’s me inspecting an image in a WordPress blog post and seeing a beefy srcset with a healthy amount of pre-generated size options and a sizes attribute tailored to this theme.
A landing page for gatsby-image explaining all of the additional image loading stuff it can do.

Responsive images go beyond HTML. In CSS, object-fit and object-position control how an image behaves inside its box — cropping, containing, or nudging instead of squishing.

For background images, @media queries serve as the CSS equivalent of <picture>: the browser downloads only the match and follows your rules exactly.

.img {
  background-image: url(small.jpg);
}
@media 
  (min-width: 468px),
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) {
  .img {
    background-image: url(large.jpg);
  }
}

The CSS counterpart to srcset/sizes is image-set(), but it's not production-ready. Safari's support leads, Chrome's has been prefixed for years, and Firefox doesn't support it. The spec only covers x descriptors so far. Until that matures, media queries are the safer choice.

Polyfills are mostly unnecessary. Nothing here breaks in old browsers as long as an <img src="" alt=""> fallback remains. Picturefill remains an option for full IE 9-11 support, though a simpler baseline assumption — IE11 on a non-retina desktop — often means simply defaulting src to a low-density image.

Broader image performance levers

  • Compression quality: Responsive markup only delivers if the source files themselves are optimized. Match each image's quality to its display size.
  • CDN serving: Geographical proximity is a meaningful speed factor in its own right.
  • Caching: HTTP caching via Cache-Control headers can eliminate repeat downloads entirely.
  • Lazy loading: Deferring off-screen images until they approach the viewport avoids wasted bandwidth for content users never scroll to.

For background reading, Eric Portis has a deep dive into srcset and sizes, MDN maintains a solid guide, and the original W3C Community Group documents the work that made these features standard. Browser support for srcset/sizes and <picture> is effectively universal in modern browsers, with details available from Caniuse.