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.
- 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
Pixel density 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
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.
Fluid Typography and Spacing
Fluid values scale smoothly between minimum and maximum without step-function breakpoints — the clamp() function makes this a single CSS declaration.
Lab: Make It Responsive
Take a desktop-only layout and make it fully responsive using mobile-first CSS, media queries, fluid typography, and responsive images.