There are exactly three jobs in responsive images, and native HTML does all three with no library, no build-time PHP, and no JavaScript:
- Resolution switching (same picture, different sizes for different screens and pixel densities):
<img srcset>withsizes. - Art direction (a genuinely different crop or image at different viewports):
<picture>with<source media>. - Format fallback (serve AVIF or WebP, fall back to JPEG):
<picture>with<source type>.
Pick the job, pick the tool. The mistake I see most often is reaching for <picture> when a one-line srcset would do, so start with the simplest one that solves your problem and only escalate when you have to.
If you arrived here from older tutorials pushing Client Hints (the DPR, Width, and Viewport-Width request headers) as the clean server-side answer: skip it. Those headers are deprecated, their Sec-CH- replacements were specced but never shipped in any browser, and the official guidance now points back to srcset/sizes. More on that at the end.
Resolution switching: srcset and sizes
This is the common case: one image, and you want the browser to download a file that suits the reader's viewport and device pixel ratio without you over-serving a 2000px file to a phone. You give the browser a menu of files with their widths, tell it roughly how wide the image will render, and let it pick.
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w, photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A description of the photo"
width="1600"
height="900"
/>The w descriptors (400w, 800w) tell the browser the intrinsic pixel width of each file. They are not viewport conditions, they are the actual width of the JPEG. The sizes attribute is your estimate of how wide the image will render in CSS pixels: here, full viewport width below 600px, otherwise half. The browser multiplies the matched sizes value by the device pixel ratio and chooses the smallest srcset file that still covers it. On a 375px-wide phone at 2x DPR that is 750 device pixels, so it grabs photo-800.jpg. The src is the fallback for browsers that ignore srcset (effectively none in 2026, but keep it).
If the image is always rendered at a fixed CSS size (an avatar, a logo) and you only care about retina sharpness, the density descriptor form is simpler and needs no sizes:
<img src="avatar.png" srcset="avatar.png 1x, avatar@2x.png 2x, avatar@3x.png 3x" alt="" width="48" height="48" />Use x descriptors for fixed-size images, w descriptors plus sizes for fluid ones. Don't mix the two forms in one srcset.
The sizes gotcha (this is the one that bites)
sizes is a promise you make to the browser about layout, and the browser believes you. If your sizes is wrong, resolution switching still "works" in the sense that nothing errors, but it picks the wrong file, and you have quietly defeated the entire point.
The classic failure is leaving sizes="100vw" (the implicit default if you omit sizes entirely) on an image that actually renders in a 400px column on desktop. The browser thinks it needs a full-viewport image, downloads the 1600px file, and you have made the page heavier than the non-responsive version. The other direction is just as bad: a too-small sizes serves a blurry image into a wide slot.
sizes has to track your real CSS layout, including media-query breakpoints, gutters, and max-width caps. If your content column maxes out at 720px, say so:
<img
srcset="img-480.jpg 480w, img-720.jpg 720w, img-1080.jpg 1080w, img-1440.jpg 1440w"
sizes="(min-width: 760px) 720px, 100vw"
alt="..."
width="1440" height="810"
/>Two practical rules. First, the order in sizes matters: it is evaluated left to right and the first matching media condition wins, so put the narrow-viewport conditions before the wide-open default. Second, when in doubt, slightly over-estimate rather than under: a marginally larger download beats a visibly soft image. And revisit sizes whenever you change the layout. It is the single most common reason "I added srcset and nothing improved."
Art direction: picture with media
Resolution switching always serves the same image, just scaled. Art direction is when you want a different image: a wide cinematic crop on desktop, but a tight square crop on mobile because the wide one renders the subject's face as twelve pixels. That is a job for <picture> with media conditions, because you are choosing between distinct sources, not asking the browser to scale one.
<picture>
<source media="(min-width: 800px)" srcset="hero-wide.jpg" />
<source media="(min-width: 500px)" srcset="hero-medium.jpg" />
<img src="hero-square.jpg" alt="..." width="600" height="600" />
</picture>The browser walks the <source> elements top to bottom and uses the first whose media matches; if none match, it falls through to the <img>. The <img> is mandatory: it is what actually renders, carries the alt, and is the fallback. A <picture> with no <img> displays nothing. You can also combine the two techniques: each <source> can carry its own srcset plus sizes, so you art-direct the crop and resolution-switch within each crop.
Format fallback: picture with type
The other use for <picture> is serving a modern format to browsers that support it while keeping a safe fallback for those that don't. You order sources best-to-worst and the browser takes the first type it can decode:
<picture>
<source type="image/avif" srcset="photo.avif" />
<source type="image/webp" srcset="photo.webp" />
<img src="photo.jpg" alt="..." width="1200" height="800" />
</picture>As of 2026 this AVIF, WebP, JPEG ladder is the right default. AVIF sits around 94% global support (Safari has decoded still AVIF since 16.0 on iOS 16 and 16.1 on macOS Ventura, with full support including animation landing in 16.4, plus Chrome, Edge, Firefox, and Samsung Internet), so the AVIF source covers the large majority; WebP catches almost all of the rest, and the JPEG <img> is the floor for the last sliver of old iOS and legacy enterprise browsers. AVIF typically beats WebP on file size at a given quality, which is why it leads. There is no harm in offering all three. The browser downloads exactly one.
You can put type and a width-descriptor srcset on the same <source>, so format fallback and resolution switching compose:
<picture>
<source type="image/avif" srcset="p-400.avif 400w, p-800.avif 800w, p-1200.avif 1200w" sizes="(min-width: 700px) 700px, 100vw" />
<source type="image/webp" srcset="p-400.webp 400w, p-800.webp 800w, p-1200.webp 1200w" sizes="(min-width: 700px) 700px, 100vw" />
<img src="p-800.jpg" alt="..." width="1200" height="800" />
</picture>Lazy loading, async decoding, and reserving space
Three attributes round out a well-behaved image and they belong on the <img> (inside <picture> too, since the <img> is what renders):
<img src="photo.jpg" srcset="..." sizes="..." alt="..." width="1200" height="800" loading="lazy" decoding="async" />loading="lazy"defers the download until the image is near the viewport. Put it on below-the-fold images only. Lazy-loading your hero or LCP image delays the most important paint and hurts Largest Contentful Paint, so leave the hero eager (the default).decoding="async"lets the browser decode the image off the main thread so it doesn't block rendering.fetchpriority="high"is the flip side of lazy loading: put it on the one above-the-fold image that is your LCP element (usually the hero) so the browser fetches it ahead of lower-priority resources. Use it on a single image per page; multiple high-priority images compete and cancel the benefit. Leave everything else at the default priority. This is the native, no-preload way to load the hero sooner.widthandheightare not optional even when CSS resizes the image. They give the browser the aspect ratio up front so it can reserve the right amount of layout space, which is what prevents Cumulative Layout Shift as images load. Set them to the intrinsic pixel dimensions and let CSS (max-width: 100%; height: auto) scale visually. That cooperation between the HTML attributes and the CSS is exactly the responsive image squishing fix, where I cover why both halves are needed.
Client Hints are effectively dead, skip them
For a while the pitch was to let the server pick the image: the browser would send DPR, Width, and Viewport-Width request headers, and your CDN would return a tailored file, no sizes math required. It was attractive on paper and a lot of 2016-era tutorials (and image-CDN marketing) built on it.
It did not survive. Those three device hints were deprecated, their privacy-conscious Sec-CH-DPR / Sec-CH-Width / Sec-CH-Viewport-Width replacements were specified but never shipped in any browser, and the current official guidance steers you straight back to srcset and sizes for responsive image selection. The header-based approach also only ever worked with a cooperating image server in the loop. If you see Client Hints recommended for responsive images today, the advice is stale. The native attributes in this article are the supported, durable path.
Quick decision guide
Same image, just needs to scale for screen/DPR? -> <img srcset> + sizes (w) , or srcset (x) if fixed size
Different crop/image at different viewports? -> <picture> + <source media>
Serve AVIF/WebP with a JPEG fallback? -> <picture> + <source type>
Need two of the above at once? -> combine: srcset+sizes on each <source>
Below the fold? -> add loading="lazy"
Hero / LCP image (one per page)? -> add fetchpriority="high", no loading="lazy"
Always: -> width + height on <img>, real alt, correct sizesFAQ
See also
- Why your responsive images look squished: max-width and height: auto: the CSS half of the story, including how the HTML
width/heightattributes andheight: autocooperate to prevent layout shift. - The CSS :has() parent selector: style a container based on what it holds, which pairs well with conditionally swapping image treatments.
- CSS filters: blur, grayscale, brightness, drop-shadow, and more: once the right image is loading,
filteris how you treat it visually.
Sources
Authoritative references this article was fact-checked against.
- Responsive images, MDN Web Docsdeveloper.mozilla.org
- The img element (srcset, sizes, loading, decoding), MDN Web Docsdeveloper.mozilla.org
- The picture element, MDN Web Docsdeveloper.mozilla.org
- Viewport-Width header (deprecated), MDN Web Docsdeveloper.mozilla.org
- AVIF image format browser support, Can I usecaniuse.com
- Optimize resource loading with the Fetch Priority API, web.devweb.dev





