Responsive Images Done Right: srcset, sizes, and Modern Formats
Images are the heaviest assets on most websites. They account for more bandwidth than HTML, CSS, and JavaScript combined. And yet, the majority of small business websites we audit are serving the exact same image file regardless of whether the visitor is on a 27-inch retina display or a five-inch phone screen on a cellular connection.
This means a mobile user on 4G is downloading a 2MB hero image that gets displayed at 375 pixels wide. They’re paying for bandwidth and waiting for load time on pixels they’ll never see.
Responsive images solve this problem. They let you serve the right image at the right size in the right format to every device. And when done correctly, they can cut your image payload by 50-80% without any visible quality loss. If you want the whole picture, we put together the full picture on Core Web Vitals and why they move rankings.
This isn’t optional optimization anymore. Google’s Core Web Vitals explicitly penalize oversized images through the Largest Contentful Paint metric. If your hero image is 1.5MB when it could be 150KB, you’re paying for it in search rankings.
Let’s walk through how responsive images work, from the HTML attributes to modern formats to the tools that make implementation practical.
The Problem: One Size Fits None
A typical approach to images on a website looks like this:
<img src="hero-image.jpg" alt="Our team at work">That single image file is served to every visitor. Whether they’re on:
- A 4K desktop monitor that could use a 2400px wide image
- A standard laptop that needs maybe 1400px
- A tablet in portrait mode that needs 768px
- A phone that needs 375px
If you optimized for the 4K monitor, phone users are downloading an image four to six times larger than they need. If you optimized for mobile, desktop users see a blurry, pixelated mess.
The responsive image specification in HTML gives us the tools to solve this properly.
srcset: Telling the Browser What’s Available
The srcset attribute lets you provide multiple versions of an image at different sizes and tell the browser the width of each one:
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w,
hero-2000.jpg 2000w"
alt="Our team at work"
>The w descriptor tells the browser the actual pixel width of each image file. The browser then uses this information (along with the viewport size and device pixel ratio) to choose the most appropriate image.
Key points:
- The browser makes the final decision. You’re providing options; the browser picks the best one based on conditions you can’t fully predict (viewport size, pixel density, network speed, user preferences).
- The
srcattribute is the fallback. Older browsers that don’t supportsrcsetwill use thesrcimage. - You don’t need to provide every possible size. Four to five sizes covering the range from mobile to large desktop is usually sufficient.

sizes: Telling the Browser How Big the Image Will Be
Here’s where it gets important. The browser needs to choose an image before it has finished loading CSS and calculating layout. It knows the viewport width, but it doesn’t yet know how wide the image will actually be displayed.
The sizes attribute tells the browser how wide the image will be at different viewport widths:
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w,
hero-2000.jpg 2000w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
alt="Our team at work"
>This tells the browser:
- On screens up to 600px wide, the image will take up 100% of the viewport width
- On screens up to 1200px wide, the image will take up 50% of the viewport width
- On everything larger, the image will take up 33% of the viewport width
With this information, the browser can calculate exactly which image file it needs. On a 375px phone screen where the image is 100vw, it needs a 375px-wide image (or 750px for a 2x retina display). On a 1400px desktop where the image is 33vw, it needs roughly a 462px-wide image.
Getting sizes Right
The sizes attribute should match your actual CSS layout. Common patterns:
Full-width hero image:
sizes="100vw"Image in a centered container with max-width:
sizes="(max-width: 1200px) 100vw, 1200px"Two-column layout on desktop, full-width on mobile:
sizes="(max-width: 768px) 100vw, 50vw"Three-column grid on desktop, two on tablet, one on mobile:
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw"If your sizes attribute doesn’t match your layout, the browser will choose the wrong image: either too large (wasting bandwidth) or too small (blurry).
The picture Element: Art Direction and Format Switching
While srcset and sizes handle resolution switching (same image at different sizes), the <picture> element handles two additional scenarios: art direction and format selection.
Art Direction
Sometimes you need a different crop or composition at different viewport sizes. A wide panoramic hero might work on desktop but become illegible on mobile. Art direction lets you serve entirely different images:
<picture>
<source media="(max-width: 600px)" srcset="hero-mobile.jpg">
<source media="(max-width: 1200px)" srcset="hero-tablet.jpg">
<img src="hero-desktop.jpg" alt="Our team at work">
</picture>On mobile, you might show a tightly cropped version focused on the key subject. On desktop, you show the full wide shot. The image is different, not just smaller.
Format Switching
This is where modern image formats come in. The <picture> element lets you offer multiple formats and let the browser choose the best one it supports:
<picture>
<source type="image/avif" srcset="hero.avif">
<source type="image/webp" srcset="hero.webp">
<img src="hero.jpg" alt="Our team at work">
</picture>The browser will use AVIF if it supports it, fall back to WebP if it doesn’t, and use JPEG as a last resort. You can combine this with srcset for full responsive + format coverage:
<picture>
<source
type="image/avif"
srcset="hero-400.avif 400w,
hero-800.avif 800w,
hero-1200.avif 1200w,
hero-1600.avif 1600w"
sizes="100vw"
>
<source
type="image/webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w,
hero-1600.webp 1600w"
sizes="100vw"
>
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w"
sizes="100vw"
alt="Our team at work"
>
</picture>Yes, the markup is verbose. But the performance gains are substantial.
Modern Image Formats: WebP and AVIF
JPEG and PNG have served us well for decades, but modern formats offer dramatically better compression.
WebP
Developed by Google, WebP provides:
- 25-35% smaller file sizes than JPEG at equivalent quality
- Support for transparency (like PNG, but smaller)
- Support for animation (like GIF, but much smaller)
- Browser support: 97%+ of all browsers as of 2026
WebP is the safe, well-supported modern format. There’s almost no reason not to use it.
AVIF
Based on the AV1 video codec, AVIF provides:
- 50% smaller file sizes than JPEG at equivalent quality
- Even better compression than WebP in most cases
- Excellent color depth and HDR support
- Browser support: 93%+ and growing
AVIF encoding is slower than WebP (which matters during build time, not for visitors), but the file size savings are remarkable. A 200KB JPEG might become a 100KB WebP or a 60KB AVIF.
Which Format to Use
Our recommendation: serve AVIF with a WebP fallback and JPEG as the final fallback. This gives the best compression to the most visitors while ensuring universal compatibility.
For sites where build time or complexity is a concern, WebP with JPEG fallback is a perfectly good approach that still delivers significant savings.
Image CDNs: The Practical Shortcut
If managing multiple image sizes and formats for every image on your site sounds overwhelming, image CDNs offer an alternative approach.
An image CDN sits between your original images and your visitors. You upload one high-resolution image, and the CDN automatically:
- Resizes it on the fly based on requested dimensions
- Converts it to the optimal format based on the visitor’s browser
- Applies compression
- Caches the result at edge locations worldwide
- Serves it from the nearest server to the visitor
Popular Image CDNs
- Cloudinary: the most feature-rich option with a generous free tier. Supports on-the-fly transformations, format conversion, and responsive breakpoints.
- imgix: high-performance image CDN with powerful URL-based transformations.
- Cloudflare Images: simple, affordable option if you’re already using Cloudflare.
- Bunny Optimizer: budget-friendly option with solid performance.
How Image CDN URLs Work
With an image CDN, your image tag might look like:
<img
src="https://res.cloudinary.com/yoursite/image/upload/w_800,f_auto,q_auto/hero.jpg"
srcset="https://res.cloudinary.com/yoursite/image/upload/w_400,f_auto,q_auto/hero.jpg 400w,
https://res.cloudinary.com/yoursite/image/upload/w_800,f_auto,q_auto/hero.jpg 800w,
https://res.cloudinary.com/yoursite/image/upload/w_1200,f_auto,q_auto/hero.jpg 1200w,
https://res.cloudinary.com/yoursite/image/upload/w_1600,f_auto,q_auto/hero.jpg 1600w"
sizes="100vw"
alt="Our team at work"
>The f_auto parameter automatically serves the best format for the browser, and q_auto applies intelligent compression. You maintain one source image, and the CDN handles everything else.
For small business websites with limited development resources, an image CDN is often the most practical path to responsive images.
Lazy Loading: Don’t Load What They Can’t See
Responsive images handle serving the right size and format. Lazy loading handles timing: don’t download images until the visitor is about to see them.
Native Lazy Loading
Modern browsers support lazy loading natively:
<img src="photo.jpg" loading="lazy" alt="Team photo">That’s it. The browser will defer loading the image until it’s near the viewport. No JavaScript required.
Important rule: Don’t lazy-load above-the-fold images. Your hero image, logo, and any images visible without scrolling should load immediately. Lazy loading these will actually hurt your LCP score.
<!-- Above the fold: load immediately, add fetchpriority -->
<img src="hero.jpg" fetchpriority="high" alt="Hero image">
<!-- Below the fold: lazy load -->
<img src="team-photo.jpg" loading="lazy" alt="Team photo">The fetchpriority="high" attribute tells the browser to prioritize downloading the hero image, which can improve LCP.
How Many Images Benefit from Lazy Loading?
For a typical small business website:
The bandwidth savings on image-heavy pages are substantial. A gallery page that would have loaded 30 full-resolution images on page load might only load four initially, with the rest loading as the visitor scrolls.
Generating Responsive Image Assets
Creating multiple sizes and formats of every image manually is tedious. Here’s how to automate it.
Build-Time Processing
Tools like Sharp (Node.js) can process images during your build step:
const sharp = require('sharp');
const sizes = [400, 800, 1200, 1600, 2000];
const formats = ['jpeg', 'webp', 'avif'];
for (const size of sizes) {
for (const format of formats) {
await sharp('original.jpg')
.resize(size)
.toFormat(format, { quality: 80 })
.toFile(`output-${size}.${format}`);
}
}This generates 15 files from one source image: three formats at five sizes. Integrated into your build process, it runs automatically on every deployment.
WordPress Solutions
For WordPress sites, plugins handle responsive images automatically:
- ShortPixel: compresses and converts images to WebP/AVIF on upload
- Imagify: similar compression and conversion with bulk processing
- EWWW Image Optimizer: handles optimization, WebP conversion, and lazy loading
WordPress also generates multiple image sizes automatically through its media handling system. Ensure your theme uses wp_get_attachment_image() which outputs proper srcset and sizes attributes automatically.
Measuring the Impact
After implementing responsive images, measure the results:
- Total page weight: check before and after on mobile and desktop
- LCP score: the primary metric responsive images improve
- Image payload per page: use Chrome DevTools Network tab filtered to images
- Core Web Vitals: check in PageSpeed Insights and Google Search Console
We typically see page weight reductions of 40-70% after implementing responsive images with modern formats. On mobile specifically, the savings are often even more dramatic because the difference between a 1600px desktop image and a 400px mobile image is massive.
A Practical Checklist
Here’s what we implement for every project:
- [ ] All images have
srcsetwith at least four size variants - [ ]
sizesattribute matches the actual CSS layout - [ ] WebP versions generated for all images (AVIF where supported)
- [ ]
<picture>element used for format switching - [ ] Above-the-fold images have
fetchpriority="high"and noloading="lazy" - [ ] Below-the-fold images have
loading="lazy" - [ ] Image generation is automated in the build process or handled by a CDN
- [ ] All images have descriptive
alttext - [ ] Image dimensions are specified to prevent layout shift