Optimization

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.

  • 26 min read
  • Updated:
  • By Convert a Document
In this 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.

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:

Traditional Implementation (Bad):
<img src="hero-2400w.jpg" alt="Hero image">
What Happens:
  • 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.

Real-World Costs:
  • 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.

Example:
<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:

  1. Viewport Width: Current browser window width
  2. sizes Attribute: How much space image will occupy
  3. Device Pixel Ratio (DPR): Display density (1x, 2x, 3x)
  4. Available Sources: Images listed in srcset

Calculation Example

Device: iPhone 13 Pro (390px viewport, 3x DPR)

  1. Viewport: 390px wide
  2. sizes: Image will be 100vw (full width) = 390px CSS pixels
  3. DPR: 3x retina display
  4. Calculation: 390px x 3 = 1170px image needed
  5. Selection: Browser chooses 1200w (closest match that's not too small)

Recommended Image Sizes

For responsive layouts, generate images at these widths:

Standard Breakpoints:
  • 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
Important: You cannot mix width (w) and density (x) descriptors in the same srcset. Choose one approach per image.

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:

CSS Layout:
.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

Mistake #1: Using 100vw When Image Isn't Full Width
<!-- Image is in 800px container with padding -->
<img sizes="100vw" ...>  <!-- WRONG -->
<img sizes="(max-width: 840px) calc(100vw - 40px), 800px" ...>  <!-- CORRECT -->
Mistake #2: Forgetting sizes with Width Descriptors
<img srcset="img-400w.jpg 400w, img-800w.jpg 800w" ...>  <!-- Missing sizes! -->

<!-- Browser assumes 100vw, may download wrong image -->

Testing sizes Accuracy

  1. Open DevTools: Chrome/Firefox Developer Tools
  2. Network Tab: Filter by Images
  3. Resize Viewport: Check which images download at different widths
  4. 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

  1. Browser evaluates source elements in order
  2. Uses first source with matching media query
  3. Falls back to img if no source matches
  4. 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

  1. Browser checks first source element
  2. If browser supports type="image/avif", uses that srcset
  3. If not, moves to next source (WebP)
  4. If WebP not supported, uses img fallback (JPG)
  5. Browser also selects appropriate size from chosen srcset based on viewport/DPR

File Size Comparison

Same Image, Different Formats (1200px width, similar quality):
  • 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

HTML:
<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)

  1. Viewport: 390px
  2. sizes match: (max-width: 640px) 100vw → 390px CSS
  3. Calculation: 390px x 3 DPR = 1170px needed
  4. Selection: 1200w (closest without being too small)

Scenario 2: iPad (768px viewport, 2x DPR)

  1. Viewport: 768px
  2. sizes match: (max-width: 1024px) 50vw → 384px CSS
  3. Calculation: 384px x 2 DPR = 768px needed
  4. Selection: 800w (closest match)

Scenario 3: Desktop (1920px viewport, 1x DPR)

  1. Viewport: 1920px
  2. sizes match: Default 800px
  3. Calculation: 800px x 1 DPR = 800px needed
  4. Selection: 800w (exact match)

Browser Behavior Notes

Important Behaviors:
  • 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.

Generate Multiple Sizes and Formats:
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.

Bash Script for Batch Processing:
#!/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

webpack.config.js:
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

vite.config.js:
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.

Dynamic Sizing:
<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).

Using the_post_thumbnail():
<?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:

functions.php:
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

Real-World Example:

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

  1. Network Tab: Filter by Images, check file sizes downloaded
  2. Coverage Tab: See unused bytes
  3. 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

  1. Open DevTools: F12 or right-click → Inspect
  2. Network Tab: Filter by "Img"
  3. Disable Cache: Check "Disable cache" option
  4. Resize Viewport: Drag window or use device emulation
  5. Refresh Page: Check which images download at different widths
  6. Verify: Ensure appropriate image sizes load

Device Emulation

Chrome DevTools:
  1. Toggle device toolbar (Ctrl+Shift+M / Cmd+Shift+M)
  2. Select device (iPhone 14, iPad, etc.)
  3. Check Network tab for downloaded image size
  4. 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:

  1. DevTools → Network tab
  2. Load page with cache disabled
  3. Look for image filenames
  4. Check size column (file size downloaded)
  5. 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:

Formula:
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

Mistake: Using 100vw when image isn't full viewport width
<!-- 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

Mistake: Omitting width/height causes Cumulative Layout Shift (CLS)
<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

Mistake: Only generating 2-3 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

Mistake: Trying to use both w and x in same srcset
<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

Mistake: Using width descriptors without sizes
<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

Mistake: Putting fallback format before modern formats
<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

Mistake: Smallest image size is still too large 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

Mistake: Only testing in desktop browser DevTools Impact: Miss real-world performance issues, bandwidth usage, image selection Fix: Test on actual mobile devices with network throttling
  • 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

Mistake: All images eager load, even below fold Impact: Slow initial page load, poor LCP, wasted bandwidth Fix: Use loading="lazy" for below-fold images
<!-- 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

Mistake: Using very low quality settings to minimize file size Impact: Visible compression artifacts, poor user experience Fix: Balance quality and size
  • 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

  1. ? Using srcset with appropriate descriptors (w or x)
  2. ? sizes attribute matches CSS layout (for width descriptors)
  3. ? width and height attributes present (prevent CLS)
  4. ? 5-8 image sizes covering device range (400w to 2000w typical)
  5. ? Modern formats (WebP/AVIF) with fallbacks
  6. ? Source order correct in picture element (modern first)
  7. ? loading="lazy" on below-fold images
  8. ? Tested on real devices and network conditions
  9. ? Lighthouse/PageSpeed scores improved
  10. ? 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.

Related articles

About Convert a Document

Convert a Document helps you understand, convert, and optimize files with simple tools and clear guidance for everyday workflows.