Web Performance · Guide
The HTML <picture> Tag (Picture Element): Format Fallbacks, Art Direction and
the 2026 Source Order
37 min read · October 14, 2026 · Mochify Engineering Team
The HTML picture tag (the <picture> element) lets you offer a browser several
versions of the same image and have it pick the first one it can use. That is the whole mechanism.
It is how a page serves a small AVIF file to the browsers that can open it, a WebP to the ones that
cannot, and a plain JPEG to everything else, from one piece of markup and with one download per
image. It is also how you show a wide photo on a laptop and a close crop of the same photo on a
phone, or a dark logo to visitors in dark mode, without any JavaScript.
If you are here because a speed test (PageSpeed Insights, Lighthouse, GTmetrix) told you to "Serve images in modern formats", the warning means your page is sending visitors the same JPEG or PNG you uploaded, when a file a fraction of the size would show the same picture. The reason it is still there is that most platforms do not fix it for you. WordPress core stores and serves uploads in the format they arrived in unless a plugin or a line of configuration says otherwise (our guide to fixing the modern-formats warning in WordPress covers that route), and hosted site builders vary: some convert, few let you choose which format goes first, and almost none will show a different crop on a phone or a different logo in dark mode. Leaving images to the platform's defaults gets you the same images as every other site on that platform. That is fine if average is the goal. If you want pages that load faster than your competitors', pass Google's Core Web Vitals checks, and look right on a phone, images are one of the few levers you can pull yourself, and this particular lever is not a chore: a few lines of markup per image, two extra files beside the one you already have, and a converter that makes them in one go.
Most explanations of the picture tag in HTML are written as reference entries: here are the attributes, good luck. This guide starts where the questions actually start. It gives you the snippet that clears the warning, walks through what a visitor on a 2026 iPhone and a visitor on an old office PC each receive from it, explains the four rules that decide whether it shows anything at all, and settles the order the formats should go in now that AVIF works for 96% of people and JPEG XL has just arrived in Chrome. Then it shows you how to make the extra files. If you have never written HTML, start with the five words below; the rest of the guide assumes only those.
Published October 14, 2026 by the Mochify Engineering Team. Written from the questions people actually bring to the picture element, with the source order and support figures dated October 2026.
Five words this guide uses
A web page is a text file, and every picture on it is a line of that text pointing at an image file. The picture element adds a short list of alternatives around that line. Everything below rests on these five terms:
<img>: the HTML tag that puts an image on a page.<img src="photo.jpg">means "show the file called photo.jpg here". It has been in HTML since the beginning and every browser, crawler and app understands it.Format: the kind of image file. JPEG and PNG are the old universal ones. WebP (2010) and AVIF (2019) store the same picture in a much smaller file, which is why speed tests ask for them. JPEG XL is the newest. Not every browser can open every format, which is the problem
<picture>exists to solve.<source>: one alternative inside a<picture>. Each<source>names a file and, optionally, a condition for using it: a format the browser must be able to decode (type), or a screen condition such as "window at least 1024 pixels wide" (media).Fallback: the version that is used when none of the alternatives apply. In a
<picture>, the fallback is always the<img>at the bottom, and it is also the only part of the whole thing that the browser actually displays.Media query: a yes/no question about the visitor's screen or settings, written in brackets:
(min-width: 1024px)means "is the window at least 1024 pixels wide?",(prefers-color-scheme: dark)means "is dark mode on?".<picture>uses them to choose between crops and between light and dark images.
The format part is where most of the benefit is, so that is where we start.
The snippet that fixes "Serve images in modern formats"
This is the markup that satisfies the audit and still works in every browser: an AVIF
source, a WebP source, and a JPEG <img> that carries everything else. You
replace an existing <img> with it, and put the two extra files next to the
JPEG.
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="A kayak on a still lake at sunrise"
width="1600" height="900" loading="lazy" decoding="async">
</picture>Read it as a set of instructions to the browser, top to bottom:
"If you can open AVIF files, use
hero.avifand stop reading.""Otherwise, if you can open WebP files, use
hero.webpand stop.""Otherwise, use
hero.jpg. Either way, this<img>line is the picture: here is its description for screen readers (alt), its size so you can leave room for it before it arrives (width,height), and permission to load it later if it is further down the page (loading="lazy")."
Here is what that means for three real visitors to the same page:
Someone on a 2026 iPhone or a current Chrome, Firefox or Edge. Their browser opens AVIF, so it downloads
hero.avifonly. For a photograph that is typically a fraction of the size of the JPEG, so the page appears sooner and uses less of their data plan. They never know the other two files exist.Someone on an older machine whose browser predates AVIF but handles WebP. Their browser skips the first line, downloads
hero.webp, and gets most of the saving.Someone on a browser that opens neither, or a tool that does not know what
<picture>means at all (an older in-app browser, a link preview bot, a search crawler). They gethero.jpg, exactly as they would have before you touched the page. Nothing breaks.
The audit this answers is Lighthouse's "Serve images in modern formats" (it was called "next-gen formats" for years, and the old name still circulates; since Lighthouse 13 it lives inside the "Improve image delivery" insight). The audit names AVIF and WebP, and its own advice is to "serve a fallback PNG or JPEG image for older browser support" (Chrome for Developers). It does not tell you how to do that. The snippet above is how.
If you already have the three files, you can stop here and paste it. If you do not, the workflow section makes them in a couple of minutes. The rest of this guide is for the moment the snippet does not behave, which, judging by the questions people ask about it, is often.
The four rules that make the picture element work
Almost every "my picture tag does not work" problem is a violation of one of four rules:
sources are read in order; type only skips what the browser cannot decode; the <img> is the only element that actually exists on the page; and a <source> inside <picture> takes srcset,
not src.
A useful way to hold all four in your head: the <picture> is an envelope,
the <source> lines are instructions written on the envelope ("if you can
read AVIF, use the AVIF"), and the <img> is the photograph inside. The envelope
is never framed and hung on the wall. The photograph is.
Rule 1: the first usable source wins, in document order
The browser does not look for the "best" source. It reads the list from the top and takes
the first one it can use. The HTML standard's selection algorithm takes the <picture> element's children "retaining relative order", walks them one by one, and for each <source> says: if it "has a media attribute, and its value
does not match the environment, continue to the next child"; if it "has a type attribute, and its value is an unknown or unsupported MIME type, continue to the next child".
The first child that survives both checks is chosen and the algorithm returns (WHATWG HTML, "updating the source set"). Nothing below it is looked at.
So the order is your preference list, and a browser that could use several of them takes the top one. If you put WebP above AVIF by mistake, every modern browser takes the WebP and the AVIF files you made are never downloaded by anyone. Put the format you most want served first: AVIF above WebP, and JPEG XL above AVIF only if you want JXL served to the browsers that now decode it (see the source order section for whether you do).
Rule 2: type is a skip instruction, not a label
The type attribute is how the browser knows, before downloading anything,
whether it can open a file. The standard says it exists "to allow the user agent to skip to
the next source element if it does not support the given type". It takes a MIME type, which
is the internet's formal name for a format: image/avif, image/webp, image/jxl, image/png. The file's extension (.avif) is
not consulted; the browser reads the type string and nothing else.
That makes the failure mode easy to predict. Leave type off a <source> and every browser assumes it can use it, including the ones that cannot.
So this:
<source srcset="hero.avif">is selected by a browser that cannot open AVIF, which then tries to decode the file, fails,
and shows a broken image instead of moving on to the JPEG. Give every format-fallback source
a type. It is one word per line and it is the difference between a safe
fallback and a blank space.
Rule 3: the <img> is the only real element
The <picture> element, in the standard's words, "merely provides a
context for its contained img element". The <source> lines
are conditions. The <img> is the thing on the page: it is what is drawn,
what CSS lays out, what screen readers announce and what search engines find. Whatever file
the browser chose, it is shown through the <img>.
That has consequences people trip over every week, and each one is a forum thread:
class,id,width,heightandstylego on the<img>. Styling the<picture>does nothing to the image. If your stylesheet says.hero { border-radius: 12px }and the class is on the<picture>, the corners stay square. Move the class to the<img>and it works.altgoes on the<img>. There is noalton<source>. This is the description a screen reader speaks and the text that shows if the image fails; it belongs on the element that is actually displayed.widthandheightgo on the<img>. The browser uses them to reserve the right-sized space before the file arrives, which is what stops the page jumping as images load. Give the real pixel dimensions of the fallback file. In a format-fallback<picture>every version should be the same picture at the same dimensions, so one pair covers all three. (MDN also allowswidthandheighton a<source>, which matters only when different crops have different shapes; see art direction.)loading,decodingandfetchprioritygo on the<img>. They are<img>attributes and apply to whichever file was chosen.loading="lazy"for anything the visitor has to scroll to;fetchpriority="high"for the one image at the top that is your Largest Contentful Paint. Our guide to optimizing hero images for LCP covers that trade-off.object-fit,object-positionand friends go on the<img>. MDN's own note: "Use these properties on the child<img>element, not the<picture>element" (MDN,<picture>).Google finds the image through the
<img>. Google Search Central recommends that "you always specify a fallback URL using thesrcattribute" because "some browsers and crawlers don't understand these attributes", and says Google can find images in thesrcattribute of an<img>even when it is a child of other elements (Google Search Central, image SEO). The<source>URLs are not where a crawler goes looking.
The single most common reason a <picture> shows nothing at all is that
the <img> is missing. People write three <source> lines,
see nothing, and assume the sources are wrong. The standard's content model is "Zero or more source elements, followed by one img element". Without the <img> there is nothing to render, in any browser, no matter how correct the
sources are.
Rule 4: <source> inside <picture> uses srcset, never src
The standard says the srcset attribute on a <source> inside <picture> "must be present", and MDN marks src as "Not
allowed if the parent is <picture>". A <source src="hero.avif"> is simply ignored; the browser moves on as if the line were not there. This is a copy-paste error
from <video> and <audio>, where <source src> is correct, and it is a quiet one: the page still shows the JPEG
fallback, nothing looks broken, and the AVIF never gets served until someone checks the Network
panel.
A srcset with a single file name is fine, and it is all the format-fallback
snippet needs. srcset can also list several sizes of the same file (hero-800.avif 800w, hero-1600.avif 1600w) with a sizes attribute beside it, which lets each format offer a small
version for phones and a large one for laptops; the comparison section covers when that extra set of files is worth
it.
Does it download every source? How to see which one loaded
No. The browser reads the <source> list before it fetches anything,
chooses one candidate, and downloads that one file; the other files are never requested. The
choice is made from the words in the markup (the type and media attributes), not by trying each file in turn, which is why type has to be an
accurate MIME type and why media is tested against the current window rather
than by trial and error. A <picture> with three alternatives costs the
visitor one download, the same as a plain <img>.
The standard's "update the image data" algorithm selects one image source from the source
set and makes one request for it. If the conditions later change, for example the visitor
turns a tablet sideways and the window crosses a media breakpoint, the browser
re-runs the selection and may request a different candidate. On first load it is one request
per <picture>.
Two ways to see which file was chosen, and neither needs any coding:
The Network panel. In Chrome, Edge or Firefox, right-click the page, choose Inspect, open the Network tab, click the Img filter, then reload the page. You will see
hero.avifin the list (in any current browser) and no request forhero.webporhero.jpg. If you see the JPEG instead in a current Chrome, Firefox or Safari, thetypevalues or thesrcsetspelling are wrong: check Rules 2 and 4.currentSrcin the console. Open the Console tab in the same panel and pastedocument.querySelector('picture img').currentSrc. It prints the address of the file the browser actually selected. The standard definescurrentSrcas "theimgelement's current request's current URL", and that request is built from the source set, which for an<img>inside<picture>includes the parent's<source>lines. So it reports the winning<source>, not just the<img src>.
Do not judge it from the Elements tab. That tab shows the markup as you
wrote it, so the <img src="hero.jpg"> is displayed there whether or not the
JPEG is what loaded.
Check your own site in sixty seconds
Open any page of your site, right-click, choose View Page Source, and
search (Ctrl+F or Cmd+F) for <picture. If it is there, your site or platform
already does this and the questions are only about the order and the files. If it is not,
every image on the page is served as whatever format you uploaded, which for most sites
means JPEG or PNG, and the snippet in this guide is the fix. If your site runs on WordPress,
our guide to fixing the "next-gen formats" warning in WordPress without plugins covers the WordPress-specific route.
Art direction: a different crop per screen with media
Art direction means showing a different composition of the same photograph depending on the
screen it is on, not just the same picture scaled down. The media attribute on <source> is how you do it: each <source> carries a condition
about the screen, and the first one whose condition is true is used.
Here is the problem it solves. Picture a shop's homepage banner: a wide photograph of a kitchen, with the product (say, a coffee grinder) on the counter at the right-hand side. On a laptop the whole scene fits and the grinder is clearly visible. On a phone, the same wide image is shrunk to a third of the width, the kitchen becomes a thin strip, and the grinder is a few pixels wide. Simply scaling the image down keeps the composition and loses the point. What you want on the phone is a tall crop with the grinder filling the frame. That is art direction: two crops of one photo, chosen by screen width.
Here is the simplest version, two crops and no format fallback yet, so the structure is clear:
<picture>
<source media="(min-width: 1024px)" srcset="kitchen-wide.jpg">
<img src="kitchen-tall.jpg" alt="A coffee grinder on a kitchen counter"
width="900" height="1200">
</picture>Read it as: "If the window is at least 1024 pixels wide, use the wide crop. Otherwise, use
the tall crop." A laptop or desktop window is almost always wider than 1024 pixels, so it
gets kitchen-wide.jpg. A phone screen is around 360 to 430 pixels wide in the units
browsers use, so the condition is false, the browser skips the <source>,
and the phone gets kitchen-tall.jpg. One file is downloaded either way.
Now add the format fallback from earlier, and the same two crops become six <source> lines plus the <img>:
<picture>
<!-- Wide screens: the wide crop, in AVIF, then WebP, then JPEG -->
<source media="(min-width: 1024px)" srcset="kitchen-wide.avif" type="image/avif">
<source media="(min-width: 1024px)" srcset="kitchen-wide.webp" type="image/webp">
<source media="(min-width: 1024px)" srcset="kitchen-wide.jpg" type="image/jpeg">
<!-- Everything else: the tall crop, in AVIF, then WebP -->
<source srcset="kitchen-tall.avif" type="image/avif">
<source srcset="kitchen-tall.webp" type="image/webp">
<!-- The fallback: the tall crop as a JPEG -->
<img src="kitchen-tall.jpg" alt="A coffee grinder on a kitchen counter"
width="900" height="1200" loading="lazy" decoding="async">
</picture>Follow two visitors through it. A visitor on a laptop with a current browser: line 1 asks
"wide window?" (yes) "and can you open AVIF?" (yes), so they get kitchen-wide.avif and the browser stops. A visitor on a three-year-old phone whose browser handles WebP but not
AVIF: lines 1 to 3 fail the "wide window?" test, line 4 passes the window test but fails the AVIF
test, line 5 passes both, so they get kitchen-tall.webp. Nobody downloads more
than one file.
The pattern is "crops times formats". Two crops and three formats is six lines; three crops would be nine. That is normal, and it is the point at which a converter that handles a folder at once (the workflow section) stops being a convenience and becomes necessary. Most images on most sites do not need art direction at all: a product thumbnail or a blog photo that just scales down is fine with the three-line format fallback. Reserve crops for the images where the small version loses the subject, which is usually the banner at the top.
Three things to get right, because each has a forum thread behind it.
Pick one direction and order the conditions accordingly. min-width conditions ("at least this wide") are true for every window larger than the number, so list the biggest breakpoint first. max-width conditions ("no wider than this") are true for every window smaller than the number, so list the smallest first. Mixing the two, or listing them
in the wrong order, is how a crop "stops working" after an edit. Here is the common version of
the bug, three breakpoints that look sensible and are not:
<!-- Broken: a 1440px laptop never gets the ultrawide crop -->
<source media="(max-width: 1023px)" srcset="kitchen-tall.jpg">
<source media="(min-width: 1024px)" srcset="kitchen-wide.jpg">
<source media="(min-width: 1440px)" srcset="kitchen-ultrawide.jpg">
<img src="kitchen-tall.jpg" alt="...">On a 1440-pixel window the browser reads line 1 (no, too wide), then line 2 ("at least
1024?" yes) and stops. Line 3 is never reached, because every window that is at least 1440
wide is also at least 1024 wide. The fix is to put the biggest number first and let the <img> cover the phones, with no max-width line at all:
<!-- Fixed: biggest first, phones fall through to the <img> -->
<source media="(min-width: 1440px)" srcset="kitchen-ultrawide.jpg">
<source media="(min-width: 1024px)" srcset="kitchen-wide.jpg">
<img src="kitchen-tall.jpg" alt="...">The safe habit is one direction per <picture>, biggest first with min-width. The classic (min-width: 1024px) form is understood by
every browser; if you prefer the newer range syntax, (width >= 1024px),
check the oldest browser you support before relying on it.
Keep the fallback <img> pointing at the crop that serves the visitors your
conditions do not cover. In the example, the phone crop is the default and the wide crop is the exception. If you reverse
it (wide crop on the <img>, tall crop behind a (max-width: 1023px) condition), the markup still works, but a crawler or an old
browser that ignores <picture> now gets the wide version, which is fine for
a banner and wrong for a portrait-shaped slot.
Different crops can have different dimensions. This is the case where width and height on a <source> earn their
place: a 1600 by 900 wide crop and a 900 by 1200 tall crop do not share a shape, so give
each <source> its own pair (width="1600" height="900" on the wide
lines) and the <img> the fallback's pair, and the browser reserves the right
box whichever it picks. Without that, the page lays out for the tall crop, then jumps when the
wide one arrives.
Dark mode images with prefers-color-scheme (and its limit)
A <source> with media="(prefers-color-scheme: dark)" serves
a different file when the visitor's phone or computer is set to dark mode, and it works
today in every current browser because it is just another media query. The typical use is a
logo: a dark wordmark on a light page, a light wordmark on a dark one, chosen automatically.
web.dev shows exactly this pattern (web.dev, prefers-color-scheme), and MDN's <picture> page uses it as its media example.
<picture>
<source srcset="logo-light-text.avif" media="(prefers-color-scheme: dark)" type="image/avif">
<source srcset="logo-light-text.png" media="(prefers-color-scheme: dark)">
<source srcset="logo-dark-text.avif" type="image/avif">
<img src="logo-dark-text.png" alt="Acme" width="160" height="40">
</picture>Read it as: "If the visitor is in dark mode, use the light-text logo (AVIF if you can, PNG if not). Otherwise, use the dark-text logo." A visitor whose iPhone switches to dark mode at sunset sees the logo change on their next page load, with no code on your side beyond those four lines.
The limit is the one that surprises people: prefers-color-scheme reports the
operating system or browser setting, not a theme switch on your own site. If your site has a
sun/moon button that adds a dark class to the page, a <picture> keyed on prefers-color-scheme ignores it, and your light-text logo appears only
when the visitor's whole phone goes dark. For a site-level toggle, the toggle has to do the
work: either put both logos on the page as two <img> elements and show
one or the other with a class, or have a few lines of script swap the src (and srcset) when the theme changes. Both are fine; <picture> on its
own is not the tool for that job.
For logos and icons, which usually have transparent backgrounds, the fallback should be a PNG, not a JPEG (a JPEG has no transparency and will paint the background a solid color), and the modern versions should be encoded without visible loss. Our guide on why WebP and AVIF cut-outs come back with black or white backgrounds explains which formats carry transparency and where it survives.
The 2026 source order: AVIF, WebP, JPEG, and where JPEG XL fits
For almost every site in October 2026 the right order is AVIF, then WebP, then the JPEG (or
PNG) fallback on the <img>. JPEG XL goes above AVIF only if you already
have JXL files and want the browsers that decode them to get them; it is not yet a reason to
make a fourth set of files.
"Support" here means the share of people whose browser can open the format. The figures come from caniuse, which combines each browser's version history with StatCounter's usage data; these are from the September 2026 data:
| Format | Global support (caniuse, Sep 2026 data) | Where it is decoded | Place in the <picture> |
|---|---|---|---|
| AVIF | 96.35% | Chrome 85+, Edge 121+, Firefox 93+, Safari 16.4+ (16.1 to 16.3 partial) | First <source> (type="image/avif") |
| WebP | 97.18% | Chrome 32+, Edge 18+, Firefox 65+, Safari 16+ (14 to 15.6 partial) | Second <source> (type="image/webp") |
| JPEG XL | 16.95% (0.02% full, 16.93% partial) | Chrome 155+ (October 6, 2026), Firefox 158+ (October 13), Safari 17+ for still images only; Edge not yet | Optional first <source> (type="image/jxl") where
you have the files |
| JPEG / PNG | effectively universal | Everything, including crawlers that only read src | The <img> |
| HEIC / HEIF | 16.93%, Safari 17+ only | Not a web format; convert it | Never in a <source> |
Read the JPEG XL row carefully. Almost all of its 16.95% is Safari's partial support, and
the September data predates Chrome 155 and Firefox 158, which shipped in the first two weeks
of October. The share will rise as those versions reach people's machines, but a rollout
takes weeks, Edge has made no statement, and nothing outside a browser (email clients, chat
apps, most CMS previews) opens a .jxl. Our guide to what Chrome 155 shipping JPEG XL changes, and what it does not yet has the dated table and the caveats per engine.
Why AVIF before WebP? Because the browsers that decode AVIF are a subset of the ones that decode WebP, and for photographs an AVIF is usually the smaller file of the two. Listing it first gives the smaller file to everyone who can take it and costs nothing to the rest, who fall through to the WebP. Our Quick Answer on whether to use WebP, AVIF or JPEG XL in 2026 covers the exceptions (very small graphics, slow encoding on huge batches, transparency edge cases), and what an AVIF file is covers the format itself.
Why keep the JPEG at all? Two reasons that will not go away in 2026. First,
roughly 3 in 100 visitors (100% minus the 97.18% WebP figure, using caniuse's September 2026
numbers) decode neither modern format: an iPhone that stopped at iOS 15, a smart TV's
browser, a locked-down corporate machine, an app's built-in web view. Without the JPEG, on a
site with 100,000 monthly visitors, that is nearly 3,000 broken images a month. Second, the <img src> is the address Google asks you to provide and the one that crawlers,
link previews and "save image as" all reach for. The fallback is cheap: it is the file you already
had.
Two notes on the fallback itself. If the image has transparency or is a flat graphic (an
icon, a diagram, a screenshot with text in it), make the fallback a PNG and ask for lossless
WebP for the WebP source; a JPEG will paint the transparent area a solid color and blur the
edges of text. And do not put a .heic in a <source>: HEIC is
the format iPhones save photos in, not a format for delivering them, and only Safari opens
it. If your source photos came off an iPhone, convert them first (our HEIC to JPG converter does a batch with no account).
Two things the picture tag cannot do: HDR and CSS backgrounds
<picture> chooses a source on exactly two tests, media and type, so it cannot choose on anything else, and it has no say over an image
that is drawn by a stylesheet rather than placed by an <img>.
It has no HDR step. Recent phones save photos with extra brightness
information (a "gain map") that makes highlights glow on an HDR screen; a JPEG that carries
one is called an Ultra HDR JPEG. The <picture> selection algorithm checks
the media query and the MIME type and nothing more. So if your fallback <img> is an Ultra HDR JPEG and you put a standard AVIF above it, every browser that opens AVIF takes
the AVIF, and the gain map is never seen by anyone, including the visitors on screens that could
have shown it. The format fallback silently downgrades the picture on exactly the displays where
it would have looked best. There is a dynamic-range media feature in CSS, and
in principle a <source media="(dynamic-range: high)"> could route HDR-capable screens to an HDR file, but we have not verified that any browser honors
it for image selection and do not recommend relying on it. If HDR matters for a given image, serve
the gain-map JPEG on its own and keep the AVIF/WebP chain for the images where it does not. Our
guide on why HDR photos look flat when you share them explains gain maps and which formats can carry one (for Mochify's own output, only JPG does; AVIF,
WebP and PNG come back standard range).
It does not apply to CSS background images. Some images are not placed with
an <img> at all; they are painted behind a section by a stylesheet rule
such as background-image: url(hero.jpg). The <picture> element has no influence over those. The stylesheet equivalent is image-set() with type() hints, which has been Baseline since September 2023, backed by a plain url() fallback for anything older; @supports tests whether the
browser understands a line of CSS, not whether it can open the file, so it is the wrong tool
for format detection. One more reason to prefer <img>: Google does not
index CSS images at all, so a photo you want to appear in image search belongs in an <img> inside a <picture>, not in a background. The CSS version has its own
short article in this series: how to give a CSS background image a WebP or AVIF fallback.
The HTML picture tag vs srcset vs a plain <img>
Use a plain <img> when nothing varies; use <img> with srcset and sizes when the only thing that varies is how big the
image needs to be; use <picture> when the format or the crop varies. The three
are not competitors so much as three sizes of the same decision, and most pages use all of them.
srcset is the part that confuses people, so in plain terms: srcset on an <img> lists the same photo at several pixel widths (photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w) and sizes tells the browser how wide the image will be displayed; the browser then
picks the smallest file that will still look sharp on that screen. It is how a phone avoids
downloading a 1600-pixel file for a 400-pixel-wide slot. It cannot say anything about
formats or crops; that is what <picture> adds.
| You need | Use | Why |
|---|---|---|
| One file, one format | <img src> | Nothing to choose between |
| Same picture at several pixel widths | <img srcset="… 800w, … 1600w" sizes="…"> | The browser picks the best resolution for the slot and screen density; it may pick a larger one than you expect, and you cannot force it |
| Different formats (AVIF, WebP, JPEG) | <picture> with type on each <source> | Format is a yes/no the browser answers from the MIME type; srcset has no
way to express it |
| Different crops per screen | <picture> with media on each <source> | A crop is an editorial decision; media makes it binding |
| Formats and sizes and crops | <picture> with media, type, and a srcset with w descriptors plus sizes on each <source> | Everything, at the cost of many files |
The distinction that explains the difference in one sentence comes from web.dev's picture
element lesson: srcset gives the browser suggestions, <picture> gives it commands. With srcset alone you describe the candidates and let the
browser pick, which is right for sizes because the browser knows the screen density, the
window width and what it has already cached, and you do not. With <picture> the media and type tests are evaluated in order and the first pass wins,
which is right for formats (a browser either opens AVIF or it does not) and for crops (you, not
the browser, decide the tall version is for phones).
A <picture> is not a performance feature by itself. It does not make an image
smaller; the smaller file has to exist. It is the delivery mechanism for files you have already
made in the right formats, which is the next section.
If you build on a framework or a site builder, you may never write any of this by hand.
Next.js's <Image> renders an <img> and picks the
format on the server from the request's Accept header (WebP by default, AVIF
optional, with art direction left to you via a <picture>); Astro's <Picture /> emits a <picture> with AVIF and WebP
sources; Eleventy Image emits an <img> or a <picture>.
Hosted platforms vary, and the sixty-second check above (search the page source for <picture) tells you what yours does. The rules in this guide are what those
tools are implementing, and they are what to check when the output looks wrong.
Mochify Workflow: making the AVIF, WebP and JPEG files
The snippet needs three files per image, and the fastest way to get them is to describe the result rather than configure it. Mochify's Magic Flow takes a plain-English instruction and applies the right format, quality and size; the same engine is behind the CLI for whole folders and the Chrome extension for a single image. None of the three needs an account for the first few files.
In the browser, for a batch
Open Magic Flow, drop the JPEGs (3 per batch with a free account, 25 on a paid plan), and type
convert to avif. Download the results into a folder calledavif. Drop the same files again and typeconvert to webp. If a photo is going into a slot that is 1200 pixels wide, say so:convert to avif, 1200px wide, and the AVIF comes back resized as well as re-encoded, which is often a bigger saving than the format change. For a logo or diagram with transparency, ask forconvert to webp, losslessand keep a PNG as the fallback; AVIF has no lossless path, so for pixel-exact graphics it is WebP or JPEG XL that you want.From the terminal, for a folder
If you are comfortable with a command line, the
mochifyCLI is a client over the same API, so it takes the same instructions and adds patterns and output folders:convert.shbrew install mochify mochify auth login # one-time browser sign-in mochify ./images/*.jpg -t avif -o ./avif mochify ./images/*.jpg -t webp -o ./webp mochify ./images/*.jpg -t avif -w 1200 -o ./avif-1200 # resized variants--losslessworks for WebP, PNG and JPEG XL output;-p "convert to avif, 1200px wide"is the Magic Flow form if you would rather describe it. Afindpipeline (find . -name "*.jpg" | mochify -t webp -o ./out) handles a whole tree of folders. The API docs have the same conversion as aPOST /v1/squishcall withtype=avifand anAuthorization: Bearerheader, for build scripts.In Chrome, for one image
Right-click any image on a page, choose Mochify, then "Convert to" and the format you want. It is a direct conversion with no prompt and no settings, one image at a time, and the file lands in your Downloads folder. The Mochify for Chrome page has the install link.
Keep the JPEGs, but make them smaller
The fallback is the file you started with, and if it was exported at maximum quality it is probably twice the size it needs to be. Drop the JPEGs into Magic Flow with the instruction
compressand they come back as JPEGs re-encoded with jpegli, which keeps the familiar JPEG that opens everywhere while spending fewer bytes at the same visual quality.
Privacy, for this workflow. Images travel to api.mochify.app over
HTTPS, are streamed into the encoder in memory, and are wiped the moment the encode finishes:
no disk writes, no logs containing file data, and never used to train AI. That holds for the web
app, the CLI and the extension alike; the CLI writes the results to your disk and nothing is kept
on the server. (Video is the one Mochify format that is processed in the browser and never leaves
the device; images are not, and we do not claim otherwise.)
Keeping them converted. Converting the existing library once is the easy half. Every image uploaded after today arrives as a JPEG or PNG and quietly undoes the fix, unless conversion is part of how images reach the site: a build step, a CLI run on a schedule, or a converter the CMS calls. The CLI commands above are the seed of that pipeline; Magic Flow at mochify.app/flow is the version with nothing to install.
Cheat Sheet
| Question | Answer |
|---|---|
Minimum working <picture> | <source srcset="x.avif" type="image/avif">, <source srcset="x.webp" type="image/webp">, <img src="x.jpg" alt="…" width="…" height="…"> |
| Source order in 2026 | AVIF, WebP, then JPEG (or PNG) on the <img>; JPEG XL first only
if you have the files |
| Which source is used | The first <source> whose media matches and whose type the browser decodes; otherwise the <img> |
What type does | Lets the browser skip a format it cannot open before any download; always set it on format-fallback sources |
Where alt, class, width, height, loading, decoding, fetchpriority, CSS go | On the <img>, never on <picture> or <source> |
src or srcset on <source> | srcset; src is ignored inside <picture> |
| Downloads every source? | No; one candidate is chosen from the markup and one file downloaded (another may
follow if a media condition changes) |
| See which loaded | DevTools Network tab filtered by Img, or img.currentSrc in the console |
| Check whether your site uses it | View Page Source, search for <picture |
| Art direction | media="(min-width: 1024px)" on the wide-crop sources, the phone crop
on the <img>; crops times formats = number of <source> lines |
min-width vs max-width | Largest breakpoint first with min-width; smallest first with max-width; one direction per <picture> |
| Dark mode | media="(prefers-color-scheme: dark)"; follows the device setting, not
your site's toggle |
| HDR | Not negotiated; an AVIF source beats an Ultra HDR JPEG fallback wherever AVIF decodes |
| CSS backgrounds | Out of scope; use image-set() with type() and a url() fallback |
| Support (caniuse, Sep 2026 data) | AVIF 96.35%, WebP 97.18%, JPEG XL 16.95% (nearly all Safari partial; Chrome 155 and Firefox 158 shipped in October) |
| Making the files | Magic Flow convert to avif / convert to webp; CLI mochify *.jpg -t avif -o ./avif |
FAQ
Why does my picture tag show nothing at all?
Almost always because the <img> is missing. The <picture> element only provides context for its <img>; the <source> elements are conditions, and without an <img> there is nothing for any browser to render. The other common cause is a <source> with no type pointing at a format the browser cannot open: the browser selects it, fails to decode it, and shows a broken image instead of falling through. Add the <img> and give every format source a MIME type.
Does the order of the source tags matter?
Yes, completely. The browser reads the <source> elements from the top and uses the first one whose media condition is true and whose type it can open; everything after it is ignored. Put the format you most want served first (AVIF above WebP), and with media conditions list the largest min-width breakpoint first or the smallest max-width first.
Does the browser download all the images in a picture element?
No. The browser decides which source to use from the media and type attributes before it downloads anything, then fetches that one file. The other sources cost nothing. If the window later crosses a media breakpoint, for example on a tablet turned sideways, the browser may fetch the newly matching file, but on first load it is one download per <picture>.
Can I put a class or width on the picture tag?
You can, but it will not do what you want. The <img> is the element that is drawn and laid out, so class, width, height, style and any CSS that targets the image need to be on the <img>. The <picture> is a wrapper with no box of its own in most layouts; styling it is like styling a <span> around the image.
Do I still need a JPEG fallback for WebP and AVIF in 2026?
Yes. In caniuse's September 2026 data 97.18% of global users have a WebP-capable browser and 96.35% an AVIF-capable one, which still leaves roughly 3 in 100 visitors who open neither, plus every crawler, link preview and tool that only reads the <img src>. Google Search Central explicitly recommends a src fallback. The JPEG is the file you already had, so keeping it costs nothing.
Does Google index images inside a picture element?
Google asks you to provide a fallback <img> with a src attribute whenever you use <picture>, and says it can find images in the src of an <img> even when that <img> is a child of other elements. Keep the <img> present, give it a real src and a descriptive alt, and the image is discoverable; the <source> URLs are not the ones to rely on for image search. Google does not index CSS background images at all.
Why does my dark-mode picture not change when I click my site's theme toggle?
Because prefers-color-scheme reports the phone's or computer's own dark-mode setting, not a switch on your site. A <source media="(prefers-color-scheme: dark)"> changes only when the visitor's device goes dark. A site-level toggle needs to do the swap itself, by showing and hiding two <img> elements with a class, or by changing src and srcset with a few lines of JavaScript.
Should I put JPEG XL in my picture tag now that Chrome supports it?
Only if you already have the JXL files. Chrome 155 (October 6, 2026) and Firefox 158 (October 13) open JPEG XL by default and Safari has opened still JXL images since version 17, but caniuse's September 2026 figure is 16.95% global, almost all of it Safari's partial support, and Edge has not shipped it. A <source srcset="x.jxl" type="image/jxl"> placed above the AVIF source is harmless, because every other browser skips it, but it is not yet a reason to make a fourth set of files.
Make the three files for your first image now at mochify.app/flow and prompt "convert to avif", then "convert to webp". Twenty-five images a month are free with an account, three without one.