Code of the Day
IntermediateResponsive Design

Responsive Images

The browser can choose from multiple image sources based on screen size and pixel density — if you provide the options with srcset and sizes.

Web FoundationsIntermediate7 min read
By the end of this lesson you will be able to:
  • Explain why a single large image wastes bandwidth on small screens
  • Write srcset and sizes attributes to give the browser multiple image candidates
  • Use the picture element for art direction and next-gen format serving
  • Apply loading="lazy" and aspect-ratio to prevent layout shift

A 2400px hero image downloaded on a 375px phone wastes most of its bandwidth before a single pixel is painted. Responsive images let the browser choose the right source for the current screen — but only if you describe the options.

The problem

A plain <img> tag has one source:

<img src="hero-2400.jpg" alt="Mountain landscape at sunrise">

Every device downloads the same file. On a phone that renders the image at 375px wide, the extra pixels are decoded, discarded, and the bandwidth is gone. On a high-density (Retina) display, a 375px image might need to be 750 physical pixels to stay crisp — but you still sent 2400px of data for a 750px need.

The browser needs choices. srcset and sizes are how you provide them.

srcset: listing your image options

srcset is a comma-separated list of image URLs, each annotated with the image's intrinsic width using a w descriptor:

<img
  src="hero-800.jpg"
  srcset="hero-400.jpg 400w,
          hero-800.jpg 800w,
          hero-1600.jpg 1600w,
          hero-2400.jpg 2400w"
  alt="Mountain landscape at sunrise"
>

The w numbers are the actual pixel dimensions of each file, not display sizes. The browser uses them to calculate which source to download — but it cannot do that without knowing how large the image will be rendered. That is what sizes is for.

sizes: telling the browser the rendered width

sizes describes how wide the image will appear at different viewport widths, written as a series of media conditions paired with lengths:

<img
  src="hero-800.jpg"
  srcset="hero-400.jpg 400w,
          hero-800.jpg 800w,
          hero-1600.jpg 1600w,
          hero-2400.jpg 2400w"
  sizes="(min-width: 1200px) 1200px,
         (min-width: 800px) 100vw,
         100vw"
  alt="Mountain landscape at sunrise"
>

Read each line as a conditional: "if the viewport is at least 1200px wide, the image will be rendered at 1200px; if it is at least 800px wide, the image fills the viewport; otherwise it also fills the viewport." The last entry has no condition — it is the fallback.

With both attributes present, the browser can calculate: rendered width × device pixel ratio = minimum file resolution needed. It then picks the smallest source that satisfies that requirement, caching the choice per device.

The src attribute is still required — it acts as the fallback for browsers that do not support srcset. Always set it to a reasonable mid-size image so the worst-case fallback is not a 2400px download.

Device pixel ratio and high-density displays

is the ratio of physical screen pixels to CSS pixels. A 2x (Retina) display packs four physical pixels into every CSS pixel square. When you render a 400px-wide image on a 2x display, the browser needs an 800px source to stay crisp.

srcset with w descriptors handles this automatically. The browser knows its own DPR and factors it into the source selection. On a 2x device at 400px rendered width, it picks the 800w source; on a 1x device at the same size, it picks the 400w source.

You do not write separate rules for retina — srcset takes care of it.

The picture element

<picture> wraps <img> and adds <source> elements that allow more explicit control. Use it for two scenarios.

Art direction: different crops for different sizes

<picture>
  <source
    media="(min-width: 800px)"
    srcset="hero-landscape-1600.jpg 1600w, hero-landscape-800.jpg 800w"
    sizes="100vw"
  >
  <img
    src="hero-portrait-400.jpg"
    srcset="hero-portrait-800.jpg 800w, hero-portrait-400.jpg 400w"
    sizes="100vw"
    alt="A chef preparing a dish in a professional kitchen"
  >
</picture>

On wide screens, the landscape crop (showing the whole kitchen) is used. On narrow screens, a tighter portrait crop (focusing on the chef's hands) works better. This is art direction — different images, not just different sizes.

Next-generation formats with a JPEG fallback

<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Coastal cliffs at low tide">
</picture>

The browser tries each <source> in order and uses the first format it supports. AVIF and WebP are typically 30–50% smaller than JPEG at the same quality. Browsers that support neither fall back to <img>. No JavaScript, no feature detection.

Preventing layout shift

When a browser starts rendering before an image has loaded, it does not know the image's dimensions and allocates no space for it. Once the image loads, the page reflows — content jumps. This is layout shift, and it hurts both usability and Core Web Vitals scores.

Two CSS declarations prevent it:

img {
  aspect-ratio: 16 / 9;   /* reserve space before the file arrives */
  width: 100%;
  height: auto;
}

aspect-ratio tells the browser to reserve a rectangle in the right proportion even before the image bytes arrive. Set width: 100% and height: auto together, and the browser uses aspect-ratio to calculate the height automatically.

Lazy loading

defers the download of images that are not yet visible in the viewport:

<img
  src="photo.jpg"
  loading="lazy"
  alt="Team photo from the 2024 summit"
>

The browser handles the intersection detection automatically. Images below the fold are fetched only as the user scrolls toward them, reducing initial page weight without a single line of JavaScript.

Do not add loading="lazy" to your hero image or any image above the fold. Lazy loading defers the fetch, which means the image loads later — the opposite of what you want for the most prominent image on the page. Reserve it for images that start off-screen.

Where to go next

You have covered every core responsive technique: viewport control, media queries, mobile-first strategy, fluid typography, and responsive images. The module closes with a hands-on lab — Lab: Make It Responsive — where you apply all five to a desktop-only layout from scratch.

Finished reading? Mark it complete to track your progress.

On this page