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.html
<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:

  1. "If you can open AVIF files, use hero.avif and stop reading."

  2. "Otherwise, if you can open WebP files, use hero.webp and stop."

  3. "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.avif only. 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 get hero.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:

picture.html
<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, height and style go 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.

  • alt goes on the <img>. There is no alt on <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.

  • width and height go 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 allows width and height on a <source>, which matters only when different crops have different shapes; see art direction.)

  • loading, decoding and fetchpriority go 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-position and 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 the src attribute" because "some browsers and crawlers don't understand these attributes", and says Google can find images in the src attribute 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:

  1. 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.avif in the list (in any current browser) and no request for hero.webp or hero.jpg. If you see the JPEG instead in a current Chrome, Firefox or Safari, the type values or the srcset spelling are wrong: check Rules 2 and 4.

  2. currentSrc in the console. Open the Console tab in the same panel and paste document.querySelector('picture img').currentSrc. It prints the address of the file the browser actually selected. The standard defines currentSrc as "the img element'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:

art-direction.html
<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>:

art-direction.html
<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:

breakpoints.html
<!-- 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:

breakpoints.html
<!-- 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.

dark-mode.html
<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:

FormatGlobal support (caniuse, Sep 2026 data)Where it is decodedPlace in the <picture>
AVIF96.35%Chrome 85+, Edge 121+, Firefox 93+, Safari 16.4+ (16.1 to 16.3 partial)First <source> (type="image/avif")
WebP97.18%Chrome 32+, Edge 18+, Firefox 65+, Safari 16+ (14 to 15.6 partial)Second <source> (type="image/webp")
JPEG XL16.95% (0.02% full, 16.93% partial)Chrome 155+ (October 6, 2026), Firefox 158+ (October 13), Safari 17+ for still images only; Edge not yetOptional first <source> (type="image/jxl") where you have the files
JPEG / PNGeffectively universalEverything, including crawlers that only read srcThe <img>
HEIC / HEIF16.93%, Safari 17+ onlyNot a web format; convert itNever 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 needUseWhy
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.

  1. 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 called avif. Drop the same files again and type convert 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 for convert to webp, lossless and 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.

  2. From the terminal, for a folder

    If you are comfortable with a command line, the mochify CLI is a client over the same API, so it takes the same instructions and adds patterns and output folders:

    convert.sh
    brew 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

    --lossless works for WebP, PNG and JPEG XL output; -p "convert to avif, 1200px wide" is the Magic Flow form if you would rather describe it. A find pipeline (find . -name "*.jpg" | mochify -t webp -o ./out) handles a whole tree of folders. The API docs have the same conversion as a POST /v1/squish call with type=avif and an Authorization: Bearer header, for build scripts.

  3. 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.

  4. 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 compress and 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

QuestionAnswer
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 2026AVIF, WebP, then JPEG (or PNG) on the <img>; JPEG XL first only if you have the files
Which source is usedThe first <source> whose media matches and whose type the browser decodes; otherwise the <img>
What type doesLets 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 goOn 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 loadedDevTools Network tab filtered by Img, or img.currentSrc in the console
Check whether your site uses itView Page Source, search for <picture
Art directionmedia="(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-widthLargest breakpoint first with min-width; smallest first with max-width; one direction per <picture>
Dark modemedia="(prefers-color-scheme: dark)"; follows the device setting, not your site's toggle
HDRNot negotiated; an AVIF source beats an Ultra HDR JPEG fallback wherever AVIF decodes
CSS backgroundsOut 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 filesMagic 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.