Full art direction

Combining format, size, and dark mode in one <picture>

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:

  • typeformat negotiation: "can the browser decode this encoding at all?"
  • mediaart direction: "does the viewport or environment match this rule?"
  • srcset + sizesresolution 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.

Winding single-track road through misty green Scottish highland cliffs under an overcast sky
Highland Road — portrait on mobile, landscape on desktop

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.

<picture> <!-- Portrait crop for narrow viewports — AVIF first --> <source type="image/avif" media="(max-width: 600px)" srcset="../assets/portrait-800.avif" width="800" height="1199" > <!-- Portrait crop — WebP fallback --> <source type="image/webp" media="(max-width: 600px)" srcset="../assets/portrait-800.webp" width="800" height="1199" > <!-- Landscape — AVIF, two sizes --> <source type="image/avif" srcset="../assets/landscape-800.avif 800w, ../assets/landscape-1200.avif 1200w" sizes="(max-width: 1000px) 100vw, 1000px" > <!-- Landscape — WebP, two sizes --> <source type="image/webp" srcset="../assets/landscape-800.webp 800w, ../assets/landscape-1200.webp 1200w" sizes="(max-width: 1000px) 100vw, 1000px" > <!-- Universal JPEG fallback --> <img src="../assets/landscape-800.jpg" alt="Winding single-track road…" width="800" height="533" loading="lazy" decoding="async" > </picture>

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:

  1. Media check — if a media attribute is present, does the current viewport satisfy it? If not, skip this source entirely.
  2. 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 selection on a 400 px wide screen in an AVIF-capable browser
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.