HTML Responsive Images

Responsive images let a browser choose the most appropriate image file for the current screen size, pixel density, and viewport — instead of always downloading one fixed image regardless of whether it’s viewed on a small phone or a huge desktop monitor. HTML gives you three tools for this: the srcset attribute, the sizes attribute, and the picture element. Used well, they cut wasted bandwidth, speed up page loads, and can even swap in a completely different crop of an image (“art direction”) depending on the viewport. This lesson covers all three in depth, how the browser actually decides which file to fetch, and the mistakes that quietly break responsive images.

Overview / How it works

By default, an <img> element has exactly one image source: whatever is in its src attribute. That single file is downloaded and displayed at every viewport size, scaled by CSS if needed — which is wasteful. A phone downloading the same 2000px-wide hero image as a 4K desktop monitor wastes data and slows down the page for no visual benefit, since the phone will just shrink it.

Responsive images solve this by giving the browser a menu of candidate image files, plus information about how large the image will be displayed, and letting the browser’s own image-selection algorithm pick the best one for the current device. Critically, the browser makes this decision itself — you don’t detect the screen size in JavaScript and swap the src. This matters for performance: browsers use a preload scanner that scans raw HTML for images (and other resources) before CSS is even parsed, so it can start downloading the winning image as early as possible, before layout is computed.

There are two distinct responsive-image problems, and HTML has a tool for each:

  • Resolution switching — same image, different file sizes/resolutions, so a phone gets a small file and a retina desktop gets a large one. This is handled by srcset (and sizes) directly on <img>.
  • Art direction — genuinely different images or crops depending on viewport (for example, a tightly cropped portrait on mobile vs. a wide landscape shot on desktop), or serving a modern format like WebP with a fallback. This is handled by the <picture> element with <source> children.

In both cases, the DOM still ends up containing a single rendered <img>picture is a non-visual wrapper that never renders anything itself; it only influences which resource the inner img ultimately loads. This is also why accessibility attributes like alt always belong on the img, never on picture or source.

Syntax

<img
  src="fallback.jpg"
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="Description of the image">

<picture>
  <source media="(max-width: 600px)" srcset="mobile.jpg">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Description of the image">
</picture>
Attribute Used on Purpose
srcset img, source A comma-separated list of candidate image URLs, each followed by a width descriptor (480w) or pixel-density descriptor (2x).
sizes img, source Describes how wide the image will be displayed at different viewport widths, using media conditions. Required for width (w) descriptors to work correctly.
src img Fallback image for browsers that don’t support srcset, and the final required child of a picture.
media source A media query; this source is only considered if the query matches. Used for art direction.
type source A MIME type (e.g. image/webp); this source is only considered if the browser can decode that format.

Examples

Example 1: Resolution switching with width descriptors

<img
  src="landscape-800.jpg"
  srcset="landscape-480.jpg 480w,
          landscape-800.jpg 800w,
          landscape-1200.jpg 1200w,
          landscape-1600.jpg 1600w"
  sizes="(max-width: 700px) 100vw, 50vw"
  alt="Mountain landscape at sunset">

Result: A single photo is displayed, scaled to fill the full viewport width on screens narrower than 700px, or half the viewport width on wider screens. Nothing visually different happens compared to a plain img — the difference is entirely in which file gets downloaded. On a narrow phone screen the browser fetches landscape-480.jpg; on a wide desktop window it fetches landscape-1200.jpg or landscape-1600.jpg depending on pixel density.

Each entry in srcset tells the browser the actual pixel width of that file (800w means the file is 800px wide intrinsically). The sizes attribute tells the browser how much of the viewport the image will occupy once laid out, in CSS pixels. Combining those two pieces of information lets the browser compute exactly which file is large enough — without being wastefully larger than necessary.

Example 2: Pixel-density switching for a fixed-size image

<img
  src="logo.png"
  srcset="logo.png 1x, logo-2x.png 2x, logo-3x.png 3x"
  alt="Company logo">

Result: A logo renders at the same CSS size on every device, but retina/high-DPI screens silently receive a sharper, higher-resolution file. A standard 1x display downloads logo.png; a 2x (retina) display downloads logo-2x.png instead, which contains more pixel data but is displayed at the identical visual size.

x descriptors are simpler than w descriptors and don’t need sizes, but they only make sense when the image is always displayed at one fixed size (icons, logos, avatars) — not for fluid, full-width content images.

Example 3: Art direction and format switching with picture

<picture>
  <source media="(max-width: 600px)" srcset="hero-portrait.jpg">
  <source type="image/webp" srcset="hero-wide.webp">
  <img src="hero-wide.jpg" alt="Team celebrating a product launch">
</picture>

Result: On a narrow phone screen, a tightly cropped portrait photo (hero-portrait.jpg) is displayed, showing just the key subject. On a wider screen, a wide landscape crop is shown instead, and if the browser supports WebP it silently downloads the smaller hero-wide.webp file; otherwise it falls back to hero-wide.jpg. Visually, mobile and desktop users can see genuinely different compositions of the same scene, not just a scaled version of one image.

How it works step by step

  1. While parsing the HTML, the browser’s preload scanner spots img/picture markup before CSS or full layout is ready, so it can begin fetching a candidate image early.
  2. If the element is a picture, the browser walks its source children in document order, checking each one’s media and type conditions. The first source whose conditions all match is selected; the rest are ignored entirely, even if they’d also match.
  3. If no source matches (or the element is a plain img), the browser falls back to the img element itself and its own srcset/src.
  4. The browser evaluates the winning element’s sizes attribute against the current viewport to determine the image’s expected display width in CSS pixels (defaulting to 100vw if sizes is omitted).
  5. The browser compares that display width against the width descriptors (or pixel-density descriptors) in srcset and picks the smallest candidate file that still looks sharp at the current device pixel ratio.
  6. Only that one chosen file is downloaded — every other candidate is never requested, which is exactly the bandwidth savings responsive images exist for.

Because the exact algorithm for picking among multiple valid w candidates is left to the browser (it can account for network speed, zoom level, etc.), two browsers may legitimately choose different files from the same srcset list — that’s expected and by design.

Common Mistakes

Mistake 1: Using width descriptors without sizes

<img srcset="photo-400.jpg 400w, photo-1200.jpg 1200w" alt="Photo">

Without a sizes attribute, the browser assumes the image will be displayed at 100vw — the full viewport width — even if your CSS actually renders it much smaller. On a desktop where the image is only shown at 300px wide, this can cause the browser to fetch the large 1200w file for no reason. Always pair w descriptors with an accurate sizes value:

<img
  srcset="photo-400.jpg 400w, photo-1200.jpg 1200w"
  sizes="(max-width: 500px) 100vw, 300px"
  alt="Photo">

Mistake 2: A picture element with no fallback img

<picture>
  <source media="(max-width: 600px)" srcset="mobile.jpg">
  <source media="(min-width: 601px)" srcset="desktop.jpg">
</picture>

This is invalid markup — the HTML spec requires picture to contain exactly one img as its final child, which acts as the required fallback if no source matches (for example, if every media query fails to match, or the browser doesn’t support picture at all). Always end with an img:

<picture>
  <source media="(max-width: 600px)" srcset="mobile.jpg">
  <source media="(min-width: 601px)" srcset="desktop.jpg">
  <img src="desktop.jpg" alt="Description of the image">
</picture>

Mistake 3: Mixing width and pixel-density descriptors in one srcset

<img srcset="small.jpg 480w, big.jpg 2x" alt="Invalid mixed descriptors">

The spec does not allow mixing w and x descriptors within the same srcset list — pick one descriptor type per image and stay consistent across every candidate in that list.

Best Practices

  • Use w descriptors with sizes for fluid, content-driven images (hero photos, article images) that scale with the layout.
  • Use x descriptors for fixed-size UI elements like logos and icons that never resize with the viewport.
  • Reach for picture only when you need art direction (different crops) or format negotiation (WebP/AVIF with a fallback) — for plain resolution switching, img with srcset alone is simpler and sufficient.
  • Always keep a meaningful alt attribute on the img inside a picture; screen readers only look at the img, never at source elements.
  • List source elements from most specific to least specific — the browser uses the first match, so put your narrowest media queries and modern formats first.
  • Order matters for format fallback too: put the modern format (image/webp, image/avif) before the widely-supported fallback so unsupported browsers skip it and land on the plain img.
  • Remember that CSS, not the picture/srcset attributes, controls actual layout size — these attributes only tell the browser which file to fetch.

Practice Exercises

  • Write an img with a srcset offering three widths (320w, 640w, 1024w) and a sizes attribute that makes the image full-width below 500px and half-width above it.
  • Write a picture element that shows a square-cropped image on screens narrower than 500px and a wide banner image otherwise, with a correct img fallback and alt text.
  • Take the pixel-density example from this lesson and extend it to support a 4x candidate for extremely high-density displays.

Summary

  • srcset plus sizes on img handles resolution switching — same image, multiple file sizes.
  • picture with source children handles art direction and format switching — different crops or formats depending on viewport or browser support.
  • The browser evaluates source elements in order and uses the first matching one; a required img fallback must come last inside picture.
  • sizes tells the browser the expected display width so it can pick the right file from a w-descriptor srcset; omitting it defaults to 100vw.
  • Don’t mix w and x descriptors in the same srcset list, and always include alt on the fallback img.