Responsive Images: Complete srcset, sizes & picture Element Guide
Master responsive images with srcset, sizes attribute, and picture element. Complete guide to optimizing images for all devices, improving LCP, reducing bandwidth, and implementing WebP/AVIF with fallbacks for perfect performance.
Master responsive images with srcset, sizes attribute, and picture element. Complete guide to optimizing images for all devices, improving LCP, reducing bandwidth, and implementing WebP/AVIF with fallbacks for perfect performance.
Serving the same large image to all devices--from 4K desktop monitors to mobile phones--wastes bandwidth, slows page loads, and damages Core Web Vitals scores. Responsive images using srcset, sizes, and the picture element solve this problem by delivering perfectly sized images to each device, reducing bandwidth by 40-70% while improving Largest Contentful Paint (LCP) times. This comprehensive guide covers everything from basic srcset implementation to advanced art direction techniques, format switching with WebP/AVIF, and automated workflows for production sites.
The Problem with Fixed-Size Images
Bandwidth Waste
Consider a typical hero image scenario:
<img src="hero-2400w.jpg" alt="Hero image">
- Desktop (2560px): Displays 2400px image → Appropriate size
- Laptop (1440px): Downloads 2400px, displays 1440px → 40% wasted bandwidth
- Tablet (768px): Downloads 2400px, displays 768px → 68% wasted bandwidth
- Mobile (375px): Downloads 2400px, displays 375px → 84% wasted bandwidth
Performance Impact
A 2400px wide JPG at 80% quality typically weighs 400-800KB. On mobile, you only need ~150-250KB for the same image at appropriate size.
- Bandwidth: Mobile user downloads 500KB extra per image
- LCP: Larger file = slower LCP = worse Core Web Vitals score
- User Experience: Slower page loads, especially on 3G/4G connections
- Cost: CDN bandwidth costs scale with file size
- SEO: Google penalizes slow-loading pages in rankings
The Solution: Responsive Images
Modern HTML provides three powerful tools:
- srcset: Specify multiple image sources at different sizes/resolutions
- sizes: Tell browser how large image will be displayed in different layouts
- picture: Provide art direction and format fallbacks
These work together to let browsers download only the appropriately sized image for each device, saving 40-70% bandwidth on average.
srcset Attribute Basics
What is srcset?
The srcset attribute allows you to specify multiple image sources with descriptors indicating their size or pixel density. The browser selects the most appropriate image based on device characteristics.
Basic Syntax
<img src="image-800w.jpg"
srcset="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w,
image-1600w.jpg 1600w"
sizes="100vw"
alt="Responsive image">
Key Concepts
- src: Fallback for browsers that don't support srcset (rare in 2026)
- srcset: Comma-separated list of image sources with descriptors
- Descriptor: Tells browser about the image (width or pixel density)
- sizes: Tells browser how much space image will occupy
Browser Support
Excellent support since 2015:
- Chrome: 38+ (2014)
- Firefox: 38+ (2015)
- Safari: 9+ (2015)
- Edge: 13+ (2015)
- Coverage: 97%+ global browser support
Width Descriptors (w)
Understanding Width Descriptors
Width descriptors tell the browser the actual width of the image file in pixels.
<img srcset="small.jpg 400w,
medium.jpg 800w,
large.jpg 1200w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
src="medium.jpg"
alt="Product photo">
How Browser Selects Image
The browser calculates which image to download based on:
- Viewport Width: Current browser window width
- sizes Attribute: How much space image will occupy
- Device Pixel Ratio (DPR): Display density (1x, 2x, 3x)
- Available Sources: Images listed in srcset
Calculation Example
Device: iPhone 13 Pro (390px viewport, 3x DPR)
- Viewport: 390px wide
- sizes: Image will be 100vw (full width) = 390px CSS pixels
- DPR: 3x retina display
- Calculation: 390px x 3 = 1170px image needed
- Selection: Browser chooses 1200w (closest match that's not too small)
Recommended Image Sizes
For responsive layouts, generate images at these widths:
- 320w: Small mobile (rare, optional)
- 480w: Mobile portrait
- 640w: Mobile landscape, small tablets
- 800w: Tablet portrait
- 1024w: Tablet landscape, small laptop
- 1280w: Laptop
- 1600w: Desktop
- 2000w: Large desktop
- 2400w: 4K displays (optional)
Practical Implementation
Full-Width Hero Image:
<img srcset="hero-480w.jpg 480w,
hero-800w.jpg 800w,
hero-1200w.jpg 1200w,
hero-1600w.jpg 1600w,
hero-2000w.jpg 2000w"
sizes="100vw"
src="hero-1200w.jpg"
alt="Hero image"
width="1600"
height="900">
Three-Column Layout:
<img srcset="product-300w.jpg 300w,
product-600w.jpg 600w,
product-900w.jpg 900w"
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
33vw"
src="product-600w.jpg"
alt="Product thumbnail">
When to Use Width Descriptors
- Best For: Responsive layouts where image size changes with viewport
- Use When: Image width is defined in CSS as percentage or vw units
- Perfect For: Hero images, content images, galleries, product photos
Density Descriptors (x)
Understanding Pixel Density
Density descriptors specify images for different device pixel ratios (DPR). They're simpler than width descriptors but less flexible.
Device Pixel Ratio Explained
- 1x: Standard displays (older monitors, non-retina)
- 2x: Retina displays (most modern phones, MacBook Pro)
- 3x: High-end phones (iPhone Pro models, Samsung flagships)
- 4x: Rare, some Android flagships
Syntax
<img src="image.jpg"
srcset="image.jpg 1x,
image@2x.jpg 2x,
image@3x.jpg 3x"
alt="Fixed-size image"
width="200"
height="200">
When to Use Density Descriptors
- Fixed Dimensions: Image always displayed at same CSS pixel size
- Icons: Small UI elements with fixed dimensions
- Logos: Header logos that don't change size
- Avatar Images: Profile pictures with fixed sizes
- Thumbnails: Grid layouts with fixed thumbnail dimensions
Practical Examples
Logo (Fixed 200px Width):
<img src="logo-200w.png"
srcset="logo-200w.png 1x,
logo-400w.png 2x,
logo-600w.png 3x"
alt="Company logo"
width="200"
height="50">
User Avatar (Fixed 64px):
<img src="avatar-64.jpg"
srcset="avatar-64.jpg 1x,
avatar-128.jpg 2x"
alt="User profile"
width="64"
height="64">
Density vs. Width Descriptors
| Aspect | Density (x) | Width (w) |
|---|---|---|
| Complexity | Simple, straightforward | More complex, requires sizes |
| Use Case | Fixed-size images | Responsive images |
| Flexibility | Limited | Very flexible |
| Browser Choice | Based only on DPR | Based on viewport, layout, DPR |
| Bandwidth Savings | Moderate | Excellent |
| Best For | Icons, logos, avatars | Content images, photos |
sizes Attribute Deep Dive
What is sizes?
The sizes attribute tells the browser how much horizontal space the image will occupy at different viewport widths. This is crucial for width descriptor (w) srcset to work correctly.
Basic Syntax
sizes="(media-condition) length, default-length"
Length Units
- vw (viewport width): 100vw = full viewport width, 50vw = half viewport width
- px: Fixed pixel values (e.g., 320px)
- Calculation: calc() function for complex layouts
- Default: Final value without media condition (applies if no conditions match)
Simple Examples
Full Width on All Devices:
sizes="100vw"
Full Width Mobile, Half Width Desktop:
sizes="(max-width: 768px) 100vw, 50vw"
Responsive Multi-Column Layout:
sizes="(max-width: 480px) 100vw,
(max-width: 768px) 50vw,
(max-width: 1200px) 33vw,
25vw"
Complex Layout Example
Three-column grid with container max-width and padding:
.container {
max-width: 1200px;
padding: 0 20px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
sizes Calculation:
sizes="(max-width: 640px) calc(100vw - 40px),
(max-width: 1240px) calc((100vw - 80px) / 2),
calc((1200px - 80px) / 3)"
Explanation:
- Mobile: Full width minus container padding (40px total)
- Tablet: Two columns, accounting for padding and gap
- Desktop: Three columns within max-width container
Real-World Layouts
Blog Post Content Image:
<!-- CSS: max-width: 800px; padding: 0 20px -->
<img srcset="content-400w.jpg 400w,
content-600w.jpg 600w,
content-800w.jpg 800w,
content-1000w.jpg 1000w"
sizes="(max-width: 840px) calc(100vw - 40px), 800px"
src="content-800w.jpg"
alt="Article image">
Sidebar Thumbnail (300px Fixed):
<img srcset="thumb-300w.jpg 300w,
thumb-600w.jpg 600w"
sizes="300px"
src="thumb-300w.jpg"
alt="Sidebar thumbnail">
Hero Image with Max Container Width:
<!-- Container max-width: 1600px -->
<img srcset="hero-800w.jpg 800w,
hero-1200w.jpg 1200w,
hero-1600w.jpg 1600w,
hero-2000w.jpg 2000w"
sizes="(max-width: 1600px) 100vw, 1600px"
src="hero-1600w.jpg"
alt="Hero banner">
Common sizes Mistakes
<!-- Image is in 800px container with padding -->
<img sizes="100vw" ...> <!-- WRONG -->
<img sizes="(max-width: 840px) calc(100vw - 40px), 800px" ...> <!-- CORRECT -->
<img srcset="img-400w.jpg 400w, img-800w.jpg 800w" ...> <!-- Missing sizes! -->
<!-- Browser assumes 100vw, may download wrong image -->
Testing sizes Accuracy
- Open DevTools: Chrome/Firefox Developer Tools
- Network Tab: Filter by Images
- Resize Viewport: Check which images download at different widths
- Verify: Ensure downloaded image matches expected size from sizes calculation
picture Element for Art Direction
What is Art Direction?
Art direction means serving different images or crops based on viewport size, not just different sizes of the same image. Perfect for images where important details get lost on mobile.
picture Element Syntax
<picture>
<source media="(min-width: 1200px)" srcset="wide-crop.jpg">
<source media="(min-width: 768px)" srcset="medium-crop.jpg">
<img src="mobile-crop.jpg" alt="Responsive image with art direction">
</picture>
How picture Works
- Browser evaluates source elements in order
- Uses first source with matching media query
- Falls back to img if no source matches
- img element is required (provides fallback and alt text)
Art Direction Use Cases
1. Different Crops for Mobile vs. Desktop
Portrait photo: Show full body on desktop, zoom to face on mobile.
<picture>
<source media="(min-width: 768px)"
srcset="portrait-wide-800w.jpg 800w,
portrait-wide-1200w.jpg 1200w,
portrait-wide-1600w.jpg 1600w"
sizes="(max-width: 1200px) 50vw, 600px">
<img src="portrait-mobile-480w.jpg"
srcset="portrait-mobile-480w.jpg 480w,
portrait-mobile-800w.jpg 800w"
sizes="100vw"
alt="Team member portrait">
</picture>
2. Horizontal vs. Vertical Composition
Landscape orientation on desktop, portrait on mobile.
<picture>
<source media="(min-width: 1024px)"
srcset="landscape-1600w.jpg 1600w,
landscape-2000w.jpg 2000w"
sizes="100vw">
<source media="(min-width: 640px)"
srcset="square-800w.jpg 800w,
square-1200w.jpg 1200w"
sizes="100vw">
<img src="portrait-640w.jpg"
srcset="portrait-640w.jpg 640w,
portrait-960w.jpg 960w"
sizes="100vw"
alt="Promotional banner">
</picture>
3. Focus on Different Subjects
Wide shot showing context on desktop, zoom to main subject on mobile.
<picture>
<!-- Desktop: Wide shot showing full product and environment -->
<source media="(min-width: 1024px)"
srcset="product-context-1600w.jpg">
<!-- Mobile: Close-up of product only -->
<img src="product-closeup-800w.jpg"
alt="Product showcase">
</picture>
Combining srcset and picture
Each source element can have its own srcset and sizes:
<picture>
<source media="(min-width: 1200px)"
srcset="hero-wide-1200w.jpg 1200w,
hero-wide-1600w.jpg 1600w,
hero-wide-2000w.jpg 2000w"
sizes="100vw">
<source media="(min-width: 768px)"
srcset="hero-medium-800w.jpg 800w,
hero-medium-1200w.jpg 1200w"
sizes="100vw">
<img src="hero-mobile-640w.jpg"
srcset="hero-mobile-640w.jpg 640w,
hero-mobile-960w.jpg 960w"
sizes="100vw"
alt="Hero image">
</picture>
Dark Mode Images
Serve different images for light and dark color schemes:
<picture>
<source media="(prefers-color-scheme: dark)" srcset="logo-dark.png">
<img src="logo-light.png" alt="Company logo">
</picture>
When NOT to Use picture
- Same Image, Different Sizes: Use img with srcset instead
- Format Switching Only: Use picture, but simpler approach (see next section)
- Simple Responsive Needs: img srcset is usually sufficient and simpler
Format Switching (WebP/AVIF with Fallbacks)
Why Format Switching Matters
Modern formats like WebP and AVIF provide 25-50% better compression than JPG, but not all browsers support them. The picture element enables progressive enhancement.
Browser Support (2026)
- AVIF: Chrome 85+, Firefox 93+, Safari 16+ (94% coverage)
- WebP: Chrome 23+, Firefox 65+, Safari 14+, Edge 18+ (97% coverage)
- JPG/PNG: Universal support (100%)
Format Switching Syntax
Progressive Enhancement Cascade:
<picture>
<!-- First choice: AVIF (best compression) -->
<source type="image/avif"
srcset="image-400w.avif 400w,
image-800w.avif 800w,
image-1200w.avif 1200w"
sizes="100vw">
<!-- Second choice: WebP (good compression, better support) -->
<source type="image/webp"
srcset="image-400w.webp 400w,
image-800w.webp 800w,
image-1200w.webp 1200w"
sizes="100vw">
<!-- Fallback: JPG (universal support) -->
<img src="image-800w.jpg"
srcset="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w"
sizes="100vw"
alt="Optimized image">
</picture>
How Browser Selects Format
- Browser checks first source element
- If browser supports type="image/avif", uses that srcset
- If not, moves to next source (WebP)
- If WebP not supported, uses img fallback (JPG)
- Browser also selects appropriate size from chosen srcset based on viewport/DPR
File Size Comparison
- PNG: 1.2 MB (lossless)
- JPG 90%: 450 KB
- JPG 80%: 280 KB
- WebP: 180 KB (35% smaller than JPG 80%)
- AVIF: 120 KB (57% smaller than JPG 80%)
Simplified Two-Format Approach
For most sites, WebP + JPG is sufficient (simpler, fewer files to generate):
<picture>
<source type="image/webp"
srcset="image-400w.webp 400w,
image-800w.webp 800w,
image-1200w.webp 1200w"
sizes="(max-width: 768px) 100vw, 50vw">
<img src="image-800w.jpg"
srcset="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Product image">
</picture>
Combining Format Switching with Art Direction
Different crops AND different formats:
<picture>
<!-- Desktop: Wide crop, AVIF -->
<source media="(min-width: 1024px)"
type="image/avif"
srcset="wide-1600w.avif 1600w, wide-2000w.avif 2000w">
<!-- Desktop: Wide crop, WebP fallback -->
<source media="(min-width: 1024px)"
type="image/webp"
srcset="wide-1600w.webp 1600w, wide-2000w.webp 2000w">
<!-- Desktop: Wide crop, JPG fallback -->
<source media="(min-width: 1024px)"
srcset="wide-1600w.jpg 1600w, wide-2000w.jpg 2000w">
<!-- Mobile: Portrait crop, WebP -->
<source type="image/webp"
srcset="portrait-640w.webp 640w, portrait-960w.webp 960w">
<!-- Mobile: Portrait crop, JPG fallback -->
<img src="portrait-640w.jpg"
srcset="portrait-640w.jpg 640w, portrait-960w.jpg 960w"
alt="Responsive image">
</picture>
Format Selection Strategy
Use AVIF When:
- Targeting modern browsers (96%+ support acceptable)
- Maximum compression is priority
- Willing to manage three format versions
- Have automated build process generating formats
Use WebP When:
- Need wider browser support (97%+)
- Balance between compression and complexity
- Managing two formats is preferred
- Good middle ground for most sites
Stick with JPG/PNG When:
- Legacy browser support is critical
- Build process doesn't support format generation
- Image count is low (manual optimization feasible)
- Hosting static files only
How Browsers Select Images
Selection Algorithm
Understanding browser selection helps you optimize srcset configurations:
Step 1: Parse srcset
- Browser reads all image sources and descriptors
- Creates list of available options
Step 2: Evaluate sizes
- Browser evaluates media conditions in sizes attribute
- Determines how much space image will occupy (CSS pixels)
Step 3: Calculate Required Width
- Multiplies CSS pixel width by device pixel ratio (DPR)
- Result = actual image pixels needed
- Example: 400px CSS width x 2 DPR = 800px image needed
Step 4: Select Best Match
- Chooses smallest image that meets or exceeds required width
- Prefers next size up rather than downscaling larger image
- May choose larger image if bandwidth is ample and image already cached
Example Walkthrough
<img srcset="img-400w.jpg 400w,
img-800w.jpg 800w,
img-1200w.jpg 1200w,
img-1600w.jpg 1600w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
800px"
alt="Example">
Scenario 1: iPhone 14 (390px viewport, 3x DPR)
- Viewport: 390px
- sizes match: (max-width: 640px) 100vw → 390px CSS
- Calculation: 390px x 3 DPR = 1170px needed
- Selection: 1200w (closest without being too small)
Scenario 2: iPad (768px viewport, 2x DPR)
- Viewport: 768px
- sizes match: (max-width: 1024px) 50vw → 384px CSS
- Calculation: 384px x 2 DPR = 768px needed
- Selection: 800w (closest match)
Scenario 3: Desktop (1920px viewport, 1x DPR)
- Viewport: 1920px
- sizes match: Default 800px
- Calculation: 800px x 1 DPR = 800px needed
- Selection: 800w (exact match)
Browser Behavior Notes
- Download Once: Browser won't re-download if image already loaded, even if viewport changes
- Preloader: Browser may start downloading images before CSS loads, so sizes must be in HTML
- User Override: Some browsers let users force low-bandwidth mode, affecting selection
- Caching: Cached images preferred over downloading new ones, even if theoretically sub-optimal
- Save Data Mode: When enabled, browser may select smaller images than optimal
Real-World Implementation Examples
1. E-commerce Product Page
Hero Product Image:
<picture>
<source type="image/avif"
srcset="product-hero-800w.avif 800w,
product-hero-1200w.avif 1200w,
product-hero-1600w.avif 1600w"
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
600px">
<source type="image/webp"
srcset="product-hero-800w.webp 800w,
product-hero-1200w.webp 1200w,
product-hero-1600w.webp 1600w"
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
600px">
<img src="product-hero-1200w.jpg"
srcset="product-hero-800w.jpg 800w,
product-hero-1200w.jpg 1200w,
product-hero-1600w.jpg 1600w"
sizes="(max-width: 768px) 100vw,
(max-width: 1200px) 50vw,
600px"
alt="Product name - main view"
width="1200"
height="900"
loading="eager">
</picture>
Product Thumbnails (Fixed 120px):
<img src="thumb-120w.jpg"
srcset="thumb-120w.jpg 1x,
thumb-240w.jpg 2x,
thumb-360w.jpg 3x"
alt="Product thumbnail"
width="120"
height="120"
loading="lazy">
2. Blog Article
Featured Image (Full Width on Mobile, Constrained on Desktop):
<!-- Article content max-width: 800px, padding: 20px -->
<picture>
<source type="image/webp"
srcset="article-featured-600w.webp 600w,
article-featured-900w.webp 900w,
article-featured-1200w.webp 1200w"
sizes="(max-width: 840px) calc(100vw - 40px), 800px">
<img src="article-featured-900w.jpg"
srcset="article-featured-600w.jpg 600w,
article-featured-900w.jpg 900w,
article-featured-1200w.jpg 1200w"
sizes="(max-width: 840px) calc(100vw - 40px), 800px"
alt="Article featured image"
width="800"
height="450">
</picture>
3. News/Magazine Homepage
Lead Story (Different Crops for Mobile/Desktop):
<picture>
<!-- Desktop: 16:9 landscape -->
<source media="(min-width: 1024px)"
type="image/webp"
srcset="lead-landscape-1200w.webp 1200w,
lead-landscape-1600w.webp 1600w"
sizes="(max-width: 1600px) 75vw, 1200px">
<source media="(min-width: 1024px)"
srcset="lead-landscape-1200w.jpg 1200w,
lead-landscape-1600w.jpg 1600w"
sizes="(max-width: 1600px) 75vw, 1200px">
<!-- Mobile: 4:3 crop focusing on subject -->
<source type="image/webp"
srcset="lead-mobile-600w.webp 600w,
lead-mobile-900w.webp 900w"
sizes="100vw">
<img src="lead-mobile-600w.jpg"
srcset="lead-mobile-600w.jpg 600w,
lead-mobile-900w.jpg 900w"
sizes="100vw"
alt="Lead story headline"
width="900"
height="675">
</picture>
4. Portfolio/Photography Site
Gallery Lightbox (Large High-Quality Images):
<picture>
<source type="image/avif"
srcset="gallery-1200w.avif 1200w,
gallery-1600w.avif 1600w,
gallery-2000w.avif 2000w,
gallery-2400w.avif 2400w"
sizes="100vw">
<source type="image/webp"
srcset="gallery-1200w.webp 1200w,
gallery-1600w.webp 1600w,
gallery-2000w.webp 2000w,
gallery-2400w.webp 2400w"
sizes="100vw">
<img src="gallery-1600w.jpg"
srcset="gallery-1200w.jpg 1200w,
gallery-1600w.jpg 1600w,
gallery-2000w.jpg 2000w,
gallery-2400w.jpg 2400w"
sizes="100vw"
alt="Portfolio image title"
width="2400"
height="1600"
loading="lazy">
</picture>
5. Landing Page Hero
Full-Width Background Hero with Text Overlay:
<picture>
<!-- Desktop: Horizontal composition -->
<source media="(min-width: 1024px)"
type="image/webp"
srcset="hero-landscape-1600w.webp 1600w,
hero-landscape-2000w.webp 2000w,
hero-landscape-2400w.webp 2400w"
sizes="100vw">
<source media="(min-width: 1024px)"
srcset="hero-landscape-1600w.jpg 1600w,
hero-landscape-2000w.jpg 2000w,
hero-landscape-2400w.jpg 2400w"
sizes="100vw">
<!-- Mobile: Vertical composition -->
<source type="image/webp"
srcset="hero-portrait-800w.webp 800w,
hero-portrait-1200w.webp 1200w"
sizes="100vw">
<img src="hero-portrait-800w.jpg"
srcset="hero-portrait-800w.jpg 800w,
hero-portrait-1200w.jpg 1200w"
sizes="100vw"
alt="Hero banner"
width="1200"
height="1600"
loading="eager"
fetchpriority="high">
</picture>
Automated Image Generation
Why Automation is Essential
Manually creating 3 formats x 5-8 sizes = 15-24 files per image is impractical for production sites with hundreds of images.
Build Tools
Sharp (Node.js)
Fast, powerful image processing library.
const sharp = require('sharp');
const sizes = [400, 800, 1200, 1600];
const formats = ['webp', 'avif', 'jpg'];
async function generateResponsiveImages(inputPath, outputDir) {
for (const size of sizes) {
for (const format of formats) {
const outputPath = `${outputDir}/image-${size}w.${format}`;
await sharp(inputPath)
.resize(size, null, { withoutEnlargement: true })
.toFormat(format, { quality: 80 })
.toFile(outputPath);
console.log(`Generated: ${outputPath}`);
}
}
}
generateResponsiveImages('original.jpg', 'dist/images');
ImageMagick (Command Line)
Classic image processing tool, available on most systems.
#!/bin/bash
INPUT_DIR="originals"
OUTPUT_DIR="responsive"
SIZES=(400 800 1200 1600)
for img in $INPUT_DIR/*.jpg; do
basename="${img##*/}"
name="${basename%.*}"
for size in "${SIZES[@]}"; do
# Generate JPG
convert "$img" -resize ${size}x -quality 80 \
"$OUTPUT_DIR/${name}-${size}w.jpg"
# Generate WebP
convert "$img" -resize ${size}x -quality 80 \
"$OUTPUT_DIR/${name}-${size}w.webp"
done
done
Build Process Integration
Webpack with Responsive Loader
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png)$/i,
loader: 'responsive-loader',
options: {
adapter: require('responsive-loader/sharp'),
sizes: [400, 800, 1200, 1600],
format: 'jpg',
quality: 80,
name: '[name]-[width]w.[ext]'
}
}
]
}
};
Usage in Component:
import heroImage from './hero.jpg?sizes[]=400,sizes[]=800,sizes[]=1200';
// Generates object with srcset, images, etc.
<img
srcSet={heroImage.srcSet}
src={heroImage.src}
sizes="100vw"
/>
Vite with vite-imagetools
import { imagetools } from 'vite-imagetools';
export default {
plugins: [imagetools()],
};
Usage:
import { srcset } from './image.jpg?w=400;800;1200;1600&format=webp;jpg';
<picture>
<source srcset={srcset.webp} type="image/webp" />
<img src={srcset.jpg[0].src} srcset={srcset.jpg} />
</picture>
CDN Image Services
Cloudinary
On-the-fly image transformations via URL parameters.
<img
src="https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg"
srcset="https://res.cloudinary.com/demo/image/upload/w_400,f_auto,q_auto/sample.jpg 400w,
https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg 800w,
https://res.cloudinary.com/demo/image/upload/w_1200,f_auto,q_auto/sample.jpg 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Sample image"
/>
Imgix
Similar URL-based transformations.
<img
srcset="https://assets.imgix.net/image.jpg?w=400&auto=format,compress 400w,
https://assets.imgix.net/image.jpg?w=800&auto=format,compress 800w,
https://assets.imgix.net/image.jpg?w=1200&auto=format,compress 1200w"
sizes="100vw"
src="https://assets.imgix.net/image.jpg?w=800&auto=format,compress"
/>
Cloudflare Images
Automatic format and size optimization.
<img
src="https://imagedelivery.net/account-hash/image-id/w=800,format=auto"
srcset="https://imagedelivery.net/account-hash/image-id/w=400,format=auto 400w,
https://imagedelivery.net/account-hash/image-id/w=800,format=auto 800w,
https://imagedelivery.net/account-hash/image-id/w=1200,format=auto 1200w"
/>
CMS and Framework Integration
WordPress
Native Responsive Images
WordPress automatically generates srcset for uploaded images (since WP 4.4).
<?php the_post_thumbnail('large'); ?>
<!-- Outputs: -->
<img width="1024" height="768"
src="image-1024x768.jpg"
srcset="image-300x225.jpg 300w,
image-768x576.jpg 768w,
image-1024x768.jpg 1024w,
image-1536x1152.jpg 1536w"
sizes="(max-width: 1024px) 100vw, 1024px"
alt="...">
Recommended Plugins
- EWWW Image Optimizer: Adds WebP/AVIF conversion
- Smush: Compression + lazy loading + WebP
- ShortPixel: Advanced optimization with CDN option
Custom Image Sizes:
add_theme_support('post-thumbnails');
// Add custom sizes
add_image_size('hero-desktop', 1600, 900, true);
add_image_size('hero-mobile', 800, 1200, true);
// Disable unused default sizes (save disk space)
function remove_default_image_sizes($sizes) {
unset($sizes['medium_large']);
unset($sizes['1536x1536']);
unset($sizes['2048x2048']);
return $sizes;
}
add_filter('intermediate_image_sizes_advanced', 'remove_default_image_sizes');
Next.js (React)
Next.js Image Component
Automatic responsive images with optimization.
import Image from 'next/image';
// Responsive fill
<div style={{ position: 'relative', width: '100%', height: '400px' }}>
<Image
src="/hero.jpg"
alt="Hero image"
fill
sizes="(max-width: 768px) 100vw, 50vw"
priority
/>
</div>
// Fixed dimensions
<Image
src="/product.jpg"
alt="Product"
width={600}
height={400}
sizes="(max-width: 768px) 100vw, 600px"
/>
Configuration (next.config.js):
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
domains: ['cdn.example.com'],
},
};
Gatsby (React)
gatsby-plugin-image
import { GatsbyImage, getImage } from 'gatsby-plugin-image';
export default function Page({ data }) {
const image = getImage(data.file);
return (
<GatsbyImage
image={image}
alt="Description"
loading="eager"
/>
);
}
GraphQL Query:
query {
file(relativePath: { eq: "hero.jpg" }) {
childImageSharp {
gatsbyImageData(
width: 1600
formats: [AUTO, WEBP, AVIF]
placeholder: BLURRED
)
}
}
}
Shopify
Liquid Image Filters
{%- assign image_sizes = '400,600,800,1000,1200,1600' | split: ',' -%}
<img
src="{{ product.featured_image | image_url: width: 800 }}"
srcset="{%- for size in image_sizes -%}
{{ product.featured_image | image_url: width: size }} {{ size }}w
{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}"
sizes="(max-width: 768px) 100vw, 50vw"
alt="{{ product.title }}"
loading="lazy"
width="800"
height="800"
>
Contentful (Headless CMS)
Image API
<img
src="https://images.ctfassets.net/space-id/asset-id/image.jpg?w=800&fm=webp&q=80"
srcset="https://images.ctfassets.net/space-id/asset-id/image.jpg?w=400&fm=webp&q=80 400w,
https://images.ctfassets.net/space-id/asset-id/image.jpg?w=800&fm=webp&q=80 800w,
https://images.ctfassets.net/space-id/asset-id/image.jpg?w=1200&fm=webp&q=80 1200w"
sizes="100vw"
/>
Performance Impact and Metrics
Bandwidth Savings
Before: Single 2000px JPG (600KB) served to all devices
After: Responsive images with WebP
- Mobile (375px): 480w WebP (85KB) → 86% savings
- Tablet (768px): 800w WebP (140KB) → 77% savings
- Desktop (1920px): 2000w WebP (320KB) → 47% savings
Average Savings: 65% across all devices
Largest Contentful Paint (LCP) Improvements
Case Study: Hero Image Optimization
| Metric | Before | After | Improvement |
|---|---|---|---|
| Image Size (Mobile) | 580KB | 95KB | 84% reduction |
| Download Time (3G) | 4.2s | 0.7s | 3.5s faster |
| LCP (Mobile) | 5.8s | 2.1s | 64% improvement |
| Core Web Vitals | Needs Improvement | Good | ✅ Pass |
Page Load Time Impact
E-commerce site with 20 product images per page:
- Before: 20 x 400KB = 8MB total image weight
- After (Responsive + WebP): 20 x 120KB avg = 2.4MB
- Savings: 5.6MB (70% reduction)
- Load Time (4G): 6.2s → 1.9s (4.3s improvement)
Measuring Performance
Chrome DevTools
- Network Tab: Filter by Images, check file sizes downloaded
- Coverage Tab: See unused bytes
- Performance Tab: Record page load, check LCP timing
Lighthouse
Checks for:
- "Properly size images" (ensures responsive images used)
- "Serve images in next-gen formats" (WebP/AVIF recommendation)
- LCP timing and recommendations
PageSpeed Insights
Real-world Chrome User Experience data + Lighthouse scores.
WebPageTest
- Test from multiple locations
- Different connection speeds
- Waterfall view showing image download timing
Monitoring in Production
Core Web Vitals Monitoring
- Google Search Console: Track LCP, CLS, INP for real users
- Chrome User Experience Report: 28-day rolling data
- RUM (Real User Monitoring): Tools like SpeedCurve, Calibre
Testing and Debugging Responsive Images
Visual Testing
Browser DevTools
- Open DevTools: F12 or right-click → Inspect
- Network Tab: Filter by "Img"
- Disable Cache: Check "Disable cache" option
- Resize Viewport: Drag window or use device emulation
- Refresh Page: Check which images download at different widths
- Verify: Ensure appropriate image sizes load
Device Emulation
- Toggle device toolbar (Ctrl+Shift+M / Cmd+Shift+M)
- Select device (iPhone 14, iPad, etc.)
- Check Network tab for downloaded image size
- Verify correct image for that device loads
Checking Which Image Loaded
Console Method:
// In browser console:
document.querySelectorAll('img').forEach(img => {
console.log(img.alt, img.currentSrc);
});
// Shows actual URL of image displayed, including size/format
Network Waterfall:
- DevTools → Network tab
- Load page with cache disabled
- Look for image filenames
- Check size column (file size downloaded)
- Verify expected image for viewport width
Validating srcset and sizes
Responsive Image Linter
Browser extension that highlights issues:
- Missing width/height attributes
- Incorrect sizes values
- Oversized images for layout
- Available for Chrome and Firefox
Manual Calculation Check:
Required Image Width =
(CSS pixel width from sizes) x (Device Pixel Ratio)
Example:
Viewport: 375px
sizes: "100vw"
DPR: 3x (iPhone 14 Pro)
Required: 375px x 3 = 1125px
Available in srcset:
- 400w (too small)
- 800w (too small)
- 1200w ← Browser should select this
- 1600w (unnecessarily large)
Common Issues and Fixes
Issue: Browser Downloads Largest Image Always
- Cause: Missing or incorrect sizes attribute
- Fix: Add accurate sizes attribute matching CSS layout
Issue: Wrong Format Loads
- Cause: picture source elements in wrong order
- Fix: AVIF before WebP before JPG; browser uses first match
Issue: Image Doesn't Change on Resize
- Cause: Browser doesn't re-download if image already loaded
- Fix: Hard refresh (Ctrl+Shift+R) to test, or test in new incognito window
Issue: Picture Element Fallback Not Working
- Cause: Missing img element inside picture
- Fix: picture must contain exactly one img element
Issue: Images Blurry on Retina Displays
- Cause: Not accounting for 2x/3x DPR in calculations
- Fix: Ensure srcset has images large enough for high-DPR displays
Automated Testing
Lighthouse CI
Integrate Lighthouse into CI/CD pipeline to catch regressions:
{
"ci": {
"assert": {
"assertions": {
"uses-responsive-images": ["error", {"minScore": 1}],
"modern-image-formats": ["error", {"minScore": 0.8}],
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}]
}
}
}
}
Image Analysis Tools
- ImageOptim (Mac): Analyze image compression efficiency
- Squoosh: Web-based, compare formats/quality settings
- ImageMagick identify: Check actual image dimensions
Common Mistakes and Pitfalls
1. Incorrect sizes Attribute
<!-- Image in 800px container -->
<img srcset="..." sizes="100vw" ...> <!-- WRONG -->
Impact: Browser downloads too-large image, wasting bandwidth
Fix: Match sizes to actual CSS layout
<img srcset="..."
sizes="(max-width: 840px) calc(100vw - 40px), 800px" ...>
2. Missing width and height Attributes
<img src="image.jpg" alt="..."> <!-- Missing dimensions -->
Impact: Page layout jumps when images load, poor CLS score
Fix: Always include width/height (use aspect ratio, not exact pixels)
<img src="image.jpg" width="1600" height="900" alt="...">
3. Too Few Image Sizes
srcset="small.jpg 400w, large.jpg 1600w"
Impact: Large gaps mean many devices download oversized images
Fix: Generate 5-8 sizes covering device range
srcset="img-480w.jpg 480w,
img-800w.jpg 800w,
img-1200w.jpg 1200w,
img-1600w.jpg 1600w"
4. Mixing Width and Density Descriptors
<img srcset="img-400w.jpg 400w, img-800w.jpg 2x" ...> <!-- INVALID -->
Impact: Syntax error, browser ignores srcset
Fix: Choose one approach per image
<!-- Either width descriptors: -->
<img srcset="img-400w.jpg 400w, img-800w.jpg 800w" sizes="..." ...>
<!-- OR density descriptors: -->
<img srcset="img.jpg 1x, img@2x.jpg 2x" ...>
5. Forgetting sizes with Width Descriptors
<img srcset="img-400w.jpg 400w, img-800w.jpg 800w" ...>
<!-- Missing sizes! -->
Impact: Browser assumes 100vw, may select wrong image
Fix: Always include sizes when using width descriptors
<img srcset="img-400w.jpg 400w, img-800w.jpg 800w"
sizes="(max-width: 768px) 100vw, 50vw" ...>
6. Wrong Source Order in picture
<picture>
<source type="image/jpeg" srcset="img.jpg"> <!-- JPG first -->
<source type="image/webp" srcset="img.webp"> <!-- WebP never used -->
<img src="img.jpg">
</picture>
Impact: Browser uses first matching source (JPG), WebP never loads
Fix: Modern formats first, fallback last
<picture>
<source type="image/avif" srcset="img.avif">
<source type="image/webp" srcset="img.webp">
<img src="img.jpg" alt="...">
</picture>
7. Serving Overly Large Images for Mobile
srcset="img-800w.jpg 800w, img-1600w.jpg 1600w"
<!-- No option smaller than 800px -->
Impact: Mobile (375px @ 3x = 1125px needed) downloads 1600px image
Fix: Include smaller sizes for mobile devices
srcset="img-480w.jpg 480w,
img-800w.jpg 800w,
img-1200w.jpg 1200w,
img-1600w.jpg 1600w"
8. Not Testing on Real Devices
- Use Chrome DevTools device emulation with throttling
- Test on real phones over cellular (3G/4G)
- Check Google PageSpeed Insights field data
9. Ignoring Image Lazy Loading
<!-- Above fold: eager (default or explicit) -->
<img src="hero.jpg" loading="eager" fetchpriority="high" ...>
<!-- Below fold: lazy -->
<img src="content.jpg" loading="lazy" ...>
10. Over-Compressing Images
- JPG: 75-85% quality for most photos
- WebP: 80-85% quality
- AVIF: 65-75% quality (different scale)
- Test visually, especially on retina displays
Quick Checklist
- ? Using srcset with appropriate descriptors (w or x)
- ? sizes attribute matches CSS layout (for width descriptors)
- ? width and height attributes present (prevent CLS)
- ? 5-8 image sizes covering device range (400w to 2000w typical)
- ? Modern formats (WebP/AVIF) with fallbacks
- ? Source order correct in picture element (modern first)
- ? loading="lazy" on below-fold images
- ? Tested on real devices and network conditions
- ? Lighthouse/PageSpeed scores improved
- ? LCP under 2.5s, image sizes appropriate
Conclusion
Responsive images using srcset, sizes, and the picture element are no longer optional for modern web development--they're essential for performance, user experience, and SEO success. By delivering appropriately sized images to each device, you can reduce bandwidth consumption by 40-70%, improve Largest Contentful Paint times by 50-80%, and provide faster, more efficient experiences for users across all devices and network conditions.
Start with the basics: implement srcset with width descriptors and accurate sizes attributes for your main content images. Add format switching with WebP (and optionally AVIF) using the picture element for 25-50% additional compression savings. As your comfort grows, incorporate art direction for hero images where mobile users benefit from different crops, and integrate automated image generation into your build process to eliminate the manual burden of creating dozens of image variants.
The investment in responsive images pays immediate dividends in Core Web Vitals scores, reduced CDN costs, and improved user satisfaction. Google Search Console data shows that pages passing Core Web Vitals thresholds see measurable ranking improvements, while users benefit from faster page loads--especially on mobile networks. With 2026's excellent browser support, comprehensive tooling, and proven performance benefits, there's never been a better time to implement responsive images across your entire site.
Ready to optimize?
Use Convert a Document to shrink files without sacrificing quality.