HTML picture Element Explained: Formats and Art Direction
The HTML picture element lets the browser choose between image sources by format or media query. When to use it over img srcset, with examples.
The <picture> element is an HTML wrapper that lets you offer the browser several alternative image sources and have it pick the first one that fits. Each alternative is a <source> element with conditions — an image format, a media query, or both — and a required <img> at the end serves as the fallback and the actual rendered image. Use <picture> when you need format fallbacks (serve AVIF or WebP where supported, JPEG elsewhere) or art direction (show a different crop on small screens). For plain resolution switching, <img srcset> alone is simpler and usually better.
The basic structure
<picture>
<source srcset="/img/hero.avif" type="image/avif">
<source srcset="/img/hero.webp" type="image/webp">
<img src="/img/hero.jpg" alt="Mountain lake at sunrise"
width="1600" height="900">
</picture>
The browser reads the <source> elements in order and uses the first one whose conditions it satisfies. If it supports AVIF, it takes the first source. If not, it checks WebP. If neither matches, it falls back to the <img src>.
Three details trip people up:
- The
<img>is mandatory. Without it nothing renders.<picture>itself has no visual box of its own; it’s a container that feeds a URL to the<img>. - Attributes go on the
<img>.alt,width,height,loading,decoding,fetchpriority,class, and inline styles all belong on the<img>, regardless of which source wins. Puttingalton a<source>does nothing. - Order matters. Because the first match wins, list the most preferred or most specific sources first. A
<source>with no conditions at the top would match every browser and make the rest unreachable.
Use case 1: format fallbacks
Modern formats like AVIF and WebP often produce noticeably smaller files than JPEG at similar visual quality (see WebP vs AVIF vs JPEG for the trade-offs). But you can’t safely put an AVIF in a bare <img src> and assume every browser decodes it.
The type attribute solves this. The browser looks at the MIME type and skips any source it can’t decode — before downloading anything. Only one image file is fetched. That’s the key advantage over JavaScript-based format detection, which typically has to wait for scripts to run.
You can combine type with srcset and sizes on each source, so every format still gets responsive resolution switching:
<picture>
<source type="image/avif"
srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w"
sizes="(max-width: 800px) 100vw, 800px">
<source type="image/webp"
srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
sizes="(max-width: 800px) 100vw, 800px">
<img src="/img/hero-800.jpg"
srcset="/img/hero-800.jpg 800w, /img/hero-1600.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Mountain lake at sunrise" width="1600" height="900">
</picture>
It’s verbose, which is why image CDNs and framework image components often generate this markup for you.
Use case 2: art direction
Art direction means showing a different image, not just a different size of the same one. A wide landscape banner that looks great on a desktop becomes a thin strip on a phone, with the subject too small to see. The fix is a tighter crop for narrow viewports:
<picture>
<source media="(max-width: 600px)"
srcset="/img/team-portrait.jpg"
width="600" height="800">
<img src="/img/team-wide.jpg"
alt="The engineering team outside the office"
width="1600" height="600">
</picture>
The media attribute takes a media query, just like CSS. Here, viewports up to 600px wide get the portrait crop; everything else gets the wide one.
Notice the width and height on the <source>. When crops have different aspect ratios, giving each source its own dimensions lets the browser reserve the right amount of space before the image loads, which prevents layout shift. That directly affects Cumulative Layout Shift, one of the Core Web Vitals.
One important rule: unlike srcset candidates, which are hints the browser may override, a matching media condition is a command. If the query matches, the browser must use that source. That’s what makes art direction reliable — and it’s also why you shouldn’t use media for simple resolution switching.
picture vs img srcset
The srcset and sizes attributes on a plain <img> handle a different problem: serving the same image at different resolutions so small screens don’t download huge files.
<img srcset sizes> | <picture> with <source> | |
|---|---|---|
| Primary job | Resolution switching | Format fallbacks and art direction |
| Same image, different sizes | Yes | Possible, but more verbose |
| Different crops per viewport | No | Yes, via media |
| Format negotiation | No | Yes, via type |
| Browser discretion | High — can factor in network, cache, density | None for media; first matching source wins |
| Markup cost | Low | Higher |
The browser-discretion row is the subtle one. With srcset, a browser on a slow connection might choose a lower-resolution candidate, or reuse a larger one already in cache. Forcing choices with media queries in <picture> throws that intelligence away. Reach for <picture> only when you need something srcset can’t express.
Performance considerations
Lazy loading works as usual. Put loading="lazy" on the <img>, and the whole <picture> defers. As with any image, don’t lazy-load your Largest Contentful Paint image; lazy loading is for content below the fold.
Prioritize the hero. For an above-the-fold <picture>, adding fetchpriority="high" to the <img> tells the browser to fetch it early. See fetch priority for how that hint interacts with the browser’s own scheduling.
Preloading needs matching conditions. If you preload a hero image with <link rel="preload">, the preload must use the same type, imagesrcset, imagesizes, or media logic as the <picture>, or the browser may fetch one file for the preload and a different one for the element. Mismatched preload hints are a common source of double downloads.
Read the chosen URL from the img. In JavaScript, img.currentSrc reports which source the browser actually selected — useful for debugging which branch of your markup won.
Styling and scripting
Since <picture> renders as an inline container around the <img>, style the <img> itself for sizing and object-fit:
picture img {
display: block;
width: 100%;
height: auto;
object-fit: cover;
}
When the selected source changes — for example, a phone rotates and a different media query now matches — the <img> fires a new load event and updates currentSrc. The browser handles the swap; no script is needed.
The takeaway
<picture> is a source selector for an <img>: the browser walks the <source> list, takes the first one whose type and media conditions match, and renders it through the mandatory <img>. Use it to serve modern formats with a safe fallback and to swap crops for different viewports. For simple size switching, a plain <img> with srcset and sizes is lighter and lets the browser make smarter choices. Keep alt, dimensions, and loading attributes on the <img>, and order sources from most to least preferred.
Keep reading
Takina · · 5 min read fetchpriority Explained: Priority Hints for Faster Loads
The fetchpriority attribute tells the browser which resources matter most. How high, low, and auto work, and how to use them to speed up LCP.
The Lycoris Team · · 5 min read What Is Incremental Static Regeneration (ISR)?
ISR lets a static page be rebuilt in the background after deploy, on a timer or on demand, without a full site rebuild. How it works and its tradeoffs.
Takina · · 4 min read Stale-While-Revalidate: The Caching Strategy Explained
Stale-while-revalidate serves a cached response instantly while fetching a fresh one in the background, so no request waits on the network.