Serving a desktop-sized, 4K background image to a mobile phone with a 375-pixel viewport wastes cellular data and slows down page rendering. Images already make up roughly 45% to 55% of total page weight on the average site, which means how you serve them, not just how you compress them, has a direct effect on how fast your pages load for most visitors.
Welcome back to BytebaseX. I'm Suptojit Modak, and in this eighth installment of the Image Optimization series, I'll explain how to implement responsive images using the HTML srcset attribute, the sizes attribute, and the <picture> element, along with the mistakes that make each of these silently stop working. If you missed the previous guide on choosing between SVG, PNG, and JPEG, you can read it here.
Why a Plain img Tag Isn't Enough Anymore
A standard image tag uses a single src attribute pointing to one static file:
<img src="hero-banner.jpg" alt="Website banner">
This works, but it forces every device, from a budget Android phone to an ultra-wide desktop monitor, to download the exact same file. A hero image sized for a 2560px desktop screen gets sent in full to a 375px phone screen too, where most of those pixels are thrown away the instant the browser resizes it down with CSS. That's wasted bandwidth on every single mobile visit, and on a content-heavy site, it adds up to a meaningful chunk of your total page weight.
Solving Size Selection with srcset and sizes
The srcset attribute lets you list several versions of the same image at different widths, and hand the decision of which one to download over to the browser. The browser looks at the visitor's screen width, pixel density, and sometimes connection speed, then picks the smallest file that will still look sharp.
There are two different ways to write a srcset, and mixing them is one of the most common mistakes people make with this attribute.
Width Descriptors (the Common Case)
For any image whose display size changes with the layout, like a blog cover photo or a hero banner, use width descriptors, written as a number followed by w. This tells the browser the actual pixel width of each file:
<img src="blog-cover-medium.jpg"
srcset="blog-cover-small.jpg 480w,
blog-cover-medium.jpg 800w,
blog-cover-large.jpg 1200w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px"
alt="Responsive blog post cover photo"
loading="lazy">
Width descriptors only work correctly when paired with a sizes attribute. Without sizes, the browser has to assume the image renders at 100% of the viewport width, which is wrong for almost every real layout, like a blog cover that only takes up half the screen on a tablet. Get this pairing wrong and the browser silently downloads a far bigger file than it needs to, with no error or warning anywhere, since this isn't something that breaks the page, it just quietly wastes bandwidth.
Pixel Density Descriptors (the Fixed-Size Case)
If an image stays a fixed size regardless of screen width, like a 100x100 author avatar, use density descriptors instead, written as 1x, 2x, and so on:
<img src="avatar-1x.jpg"
srcset="avatar-1x.jpg 1x, avatar-2x.jpg 2x, avatar-3x.jpg 3x"
alt="Author portrait"
width="100" height="100">
This tells the browser which file to use based on the device's pixel density (a Retina screen with a device pixel ratio of 2 will grab the 2x file), not the viewport width. A sizes attribute has no purpose here and should be left off, since there's no width decision to help the browser make.
The Mistake: Mixing the Two
Width descriptors (w) and density descriptors (x) cannot be mixed inside the same srcset. Writing something like srcset="image-480w.jpg 480w, image-2x.jpg 2x" is invalid and produces unpredictable results across browsers. Pick one system for a given image and stick to it: width descriptors plus sizes for anything that scales with the layout, density descriptors alone for anything that stays a fixed size.
How the Browser Actually Picks a File
It helps to know the actual math happening behind the scenes, since it makes debugging a lot easier when the wrong image shows up. The browser does three things: first, it checks your sizes value against the current viewport to work out the image's rendered width in CSS pixels. Second, it multiplies that rendered width by the device's pixel ratio to get the number of real pixels it needs. Third, it picks the smallest file in your srcset list that still covers that pixel count.
This is exactly why an inaccurate sizes value is such a common, invisible bug. If your CSS actually renders the image at 50% of the viewport width but your sizes attribute says 100vw, the browser thinks it needs a much bigger file than it actually does, and downloads one, even though every other part of your srcset setup is correct. Checking your actual rendered width in browser DevTools before writing your sizes value catches this before it ships.
Solving Art Direction and Format Fallbacks with picture
While srcset helps the browser choose between different sizes of the same image, the <picture> element handles two different problems entirely: showing a different image depending on screen size, and offering multiple file formats.
Art Direction: Different Crops for Different Screens
A wide horizontal banner that looks great on a desktop monitor can become unreadable once it's squeezed down to a mobile screen; the subject gets tiny and the composition falls apart. The <picture> element lets you serve a completely different image, not just a smaller version of the same one, depending on the viewport:
<picture>
<source media="(max-width: 600px)" srcset="hero-square-mobile.jpg">
<source media="(min-width: 601px)" srcset="hero-wide-desktop.jpg">
<img src="hero-wide-desktop.jpg" alt="Promotional hero banner" width="1200" height="600">
</picture>
This is a genuinely different job from srcset. With srcset alone, every device gets the same photo at a different resolution. With <picture> and media conditions, you can send a tightly cropped square version to mobile and a wide landscape version to desktop, which is what "art direction" means in this context.
Format Fallbacks: Serving AVIF or WebP with a Safety Net
The other job <picture> does well is offering a modern format first, with an older format as a fallback for the small slice of browsers that don't support it:
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="High resolution nature photograph" width="800" height="500" loading="lazy">
</picture>
The browser reads the <source> tags in order and uses the first format it understands. As covered earlier in this series, AVIF support sits at roughly 93% to 95% globally and WebP at around 96% to 98%, so this isn't quite the "100% compatibility for everyone" some guides claim, but it's close enough that the fallback chain covers essentially every real visitor, including the small number on older devices that fall through to the plain JPEG at the end.
srcset vs picture: Which One Do You Actually Need?
| Requirement | Recommended Approach | Key Benefit |
|---|---|---|
| Same image, different device sizes | <img srcset="..." sizes="..."> |
Browser picks the right file automatically based on screen width and pixel density. |
| Different crop or aspect ratio per screen | <picture> with media conditions |
Lets you enforce a specific composition or crop at each breakpoint. |
| Next-gen format with a fallback | <picture> with type attributes |
Serves AVIF or WebP first, with JPEG as a safety net for the rest. |
Most sites need srcset and sizes on nearly every content image, since resolution scaling is a near-universal need. <picture> is more situational: reach for it specifically when you need a different crop per device, or when you want to layer AVIF and WebP fallbacks on top of a photo. It's common, and completely fine, to combine both: a <picture> element whose fallback <img> tag also carries its own srcset and sizes, covering format and resolution at the same time.
A Quick Way to Check Your Work
After setting any of this up, open your browser's DevTools, go to the Network tab, and reload the page at a few different viewport widths using the responsive design mode. Look at which file actually got downloaded for each image at each width. If you consistently see the largest file loading even on a narrow mobile viewport, your sizes value doesn't match your real CSS layout, which is the single most common reason this setup fails to deliver any savings even though the markup looks correct.
Conclusion
Responsive images remove a real, measurable amount of wasted payload from mobile devices, and they do it without any loss in sharpness on high-resolution screens, since the right file for each device is exactly the point. The two pieces work together rather than compete: srcset and sizes handle resolution, <picture> handles art direction and format fallbacks, and most real setups end up using both. The part worth double-checking, always, is that your sizes value actually matches what your CSS renders, since that mismatch is the one mistake that makes this whole setup quietly do nothing.
This post is part of the ongoing Image Optimization series on BytebaseX. In the next guide, I'll look at how heavy images impact Core Web Vitals, specifically LCP, and how to prioritize the above-the-fold assets that matter most for that score.