Introduction
A <picture> element can carry three kinds of <source> hints at once.
Tutorial 06: Responsive Images covered
srcset and sizes for resolution switching.
Tutorial 07: picture & Art Direction
added the media attribute for swapping crops or compositions at a breakpoint.
This example stacks all three so you can see how they interact.
The three attributes serve distinct purposes and the browser treats them independently:
type— format negotiation: "can the browser decode this encoding at all?"media— art direction: "does the viewport or environment match this rule?"srcset+sizes— resolution switching: "of the candidates in this source, which pixel density fits best?"
The browser stops at the first <source> that passes both the type check
and the media check, then applies srcset/sizes to
pick a specific URL from that source's candidate list. The <img> at the end is the
universal fallback and the element that carries alt, width, and height.
Live demo
The <picture> below serves:
- A portrait crop (800×1199) on narrow viewports (≤ 600 px wide), in AVIF or WebP
- A landscape crop (800 w / 1200 w candidates) on wider viewports, also in AVIF or WebP
- A JPEG fallback for browsers that support neither modern format
Resize your browser window and reload to see the portrait source activate on a narrow screen. In a browser that does not support AVIF, the WebP sources will be selected instead.
The markup
Study the source order carefully. Portrait sources come first because they carry a
media constraint that disqualifies them on wide screens. The landscape
sources have no media attribute — they match any remaining viewport —
so they go after the portrait group. Within each group, AVIF precedes WebP because
AVIF compresses better; the browser stops as soon as it finds a supported
type.
How the browser picks a source
The browser walks the <source> list from top to bottom. For each element it
evaluates two conditions in sequence:
- Media check — if a
mediaattribute is present, does the current viewport satisfy it? If not, skip this source entirely. - Type check — does the browser support the MIME type named in
type? If not, skip.
The first source that passes both checks wins. The browser then reads its
srcset and sizes to compute the best URL, exactly as it
would for a standalone srcset on an <img>. Only one
source is ever selected per load; the rest are ignored.
| Source | media passes? | type supported? | Result |
|---|---|---|---|
type=avif media=(max-width:600px) portrait |
Yes (400 ≤ 600) | Yes | Selected — portrait-800.avif used |
type=webp media=(max-width:600px) portrait |
— | — | Skipped (earlier source already won) |
type=avif landscape 800w/1200w |
— | — | Skipped |
type=webp landscape 800w/1200w |
— | — | Skipped |
<img> JPEG fallback |
— | — | Skipped |
The source explosion
Notice that combining three concerns multiplies the number of <source>
elements fast. This example has four sources. A real-world production scenario often needs more:
- Formats: AVIF, WebP, JPEG (3)
- Art-direction breakpoints: portrait (≤ 600 px), landscape (default) (2)
- Size steps per crop: 400 w, 800 w, 1200 w, 1600 w (4)
That gives up to 3 × 2 × 4 = 24 source files and
several <source> elements. Writing that markup and generating
those files by hand does not scale. In practice, you reach for a build tool —
an image processing pipeline in Vite, Next.js, Astro, or a dedicated CLI like
sharp — that accepts the original master image and emits the full matrix
automatically.