Sirv Content Hub
Tutorial
January 28, 2026·4 min read

Implementing Responsive Images with srcset and Sirv

A hands-on tutorial covering srcset, sizes, picture elements, pixel density descriptors, and CDN-powered responsive images for optimal performance.

S
Sirv Team
Implementing Responsive Images with srcset and Sirv

What You’ll Learn

By the end of this tutorial, you’ll know how to:

  • Use srcset with width descriptors (w) for resolution switching
  • Use srcset with pixel density descriptors (x) for retina images
  • Write sizes attributes that match your CSS layout
  • Apply art direction with <picture> for different crops at different breakpoints
  • Combine responsive images with an image CDN to avoid maintaining multiple files
  • Test and debug responsive images in the browser
  • Handle common pitfalls (missing sizes, CLS, lazy loading interactions)

Why Should You Care About Responsive Images?

A product image that looks sharp on a 27” iMac is wildly oversized for a phone screen. Without responsive images, every visitor downloads the largest version. That’s a huge waste of bandwidth on mobile, and it murders your load times.

The numbers are stark:

  • The median webpage serves over 1 MB of images (HTTP Archive, January 2026)
  • A properly sized image can be 50-80% smaller than a one-size-fits-all approach
  • LCP (Largest Contentful Paint) improves directly when the hero image is right-sized: smaller file = faster download = lower LCP

Responsive images fix this by giving the browser a menu of image sources and letting it pick the best one for the current screen.

The Two Approaches

HTML gives you two ways to do responsive images:

  1. srcset + sizes: You provide candidates, the browser decides. It picks based on viewport width and pixel density.
  2. <picture> element: You control exactly which image loads at each breakpoint. This is for “art direction,” where you need different crops or aspect ratios.

Most use cases need approach #1. Only reach for approach #2 when the image itself needs to change (like a wide panorama on desktop but a tight portrait crop on mobile).

Step 1: srcset with Width Descriptors

The w descriptor tells the browser the intrinsic width of each candidate:

<img
  src="https://demo.sirv.com/product.jpg?w=800"
  srcset="
    https://demo.sirv.com/product.jpg?w=400 400w,
    https://demo.sirv.com/product.jpg?w=800 800w,
    https://demo.sirv.com/product.jpg?w=1200 1200w,
    https://demo.sirv.com/product.jpg?w=1600 1600w
  "
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  alt="Responsive product photo"
  width="800"
  height="600"
/>

How the browser decides

The browser reads sizes to figure out how wide the image will render, then picks the smallest srcset candidate that’s big enough, factoring in the device pixel ratio (DPR).

Take a 375px-wide iPhone (DPR 3):

  1. sizes says 100vw, so the image renders at 375px
  2. At 3x DPR, the browser wants at least 375 x 3 = 1125 pixels
  3. It grabs the 1200w candidate (the smallest one that clears 1125)

Now a 1440px desktop (DPR 1):

  1. sizes says 33vw, so the image renders at roughly 475px
  2. At 1x DPR, 475 pixels is all it needs
  3. It grabs the 800w candidate

Choosing your width values

Don’t try to match specific devices. Base your widths on your actual layout instead:

  • Check your image’s rendered width at common viewport sizes
  • Create candidates at roughly 1.5-2x intervals
  • 4-6 candidates typically covers the full range

A practical set for a product grid: 400w, 600w, 800w, 1200w, 1600w.

Step 2: Writing the sizes Attribute

sizes is a comma-separated list of media conditions paired with lengths. The browser uses the first match:

sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"

This reads as:

  • On viewports up to 640px: image takes full width (100vw)
  • On viewports up to 1024px: image takes half width (50vw)
  • Otherwise: image takes one-third width (33vw)

Match sizes to your CSS

Your sizes attribute must reflect your actual CSS layout. If your CSS says the image container is calc(50% - 2rem) on desktop, your sizes should approximate that:

sizes="(min-width: 1024px) calc(50vw - 2rem), 100vw"

What happens without sizes?

Skip sizes and the browser defaults to 100vw. It assumes the image fills the entire viewport. On a 2x retina screen, that means it downloads the biggest available image even if the image only takes up 25% of the screen. Always include sizes.

Step 3: srcset with Pixel Density Descriptors

For fixed-width images (logos, avatars, icons), use density descriptors instead of width descriptors:

<img
  src="https://demo.sirv.com/logo.png?w=200"
  srcset="
    https://demo.sirv.com/logo.png?w=200 1x,
    https://demo.sirv.com/logo.png?w=400 2x,
    https://demo.sirv.com/logo.png?w=600 3x
  "
  alt="Company logo"
  width="200"
  height="50"
/>

The browser picks based on the device’s pixel ratio:

  • Standard display (1x DPR): downloads the 200px version
  • Retina (2x DPR): downloads the 400px version
  • iPhone Pro (3x DPR): downloads the 600px version

When to use which:

  • w descriptors: Fluid images that change size with the viewport (product grids, heroes, content images)
  • x descriptors: Fixed-size images that stay the same regardless of viewport (logos, avatars, icons, thumbnails)

Step 4: Art Direction with picture

Need different crops at different breakpoints, not just different sizes? That’s what <picture> is for:

<picture>
  <!-- Mobile: tight crop on the product -->
  <source
    media="(max-width: 640px)"
    srcset="https://demo.sirv.com/product.jpg?w=640&h=640&crop.type=face"
  />

  <!-- Tablet: medium crop -->
  <source
    media="(max-width: 1024px)"
    srcset="https://demo.sirv.com/product.jpg?w=1024&h=600&scale.option=fill"
  />

  <!-- Desktop: wide hero shot -->
  <img
    src="https://demo.sirv.com/product.jpg?w=1400&h=500&scale.option=fill"
    alt="Product hero image"
    width="1400"
    height="500"
  />
</picture>

With Sirv’s dynamic imaging, you don’t need to manually create these crops. Just adjust the URL parameters for each breakpoint. The crop.type=face parameter auto-detects faces for smart cropping.

Combining picture with srcset

Want maximum control? Combine <picture> for art direction with srcset for resolution switching within each breakpoint:

<picture>
  <source
    media="(max-width: 640px)"
    srcset="
      https://demo.sirv.com/product.jpg?w=400&h=400&scale.option=fill 400w,
      https://demo.sirv.com/product.jpg?w=800&h=800&scale.option=fill 800w
    "
    sizes="100vw"
  />
  <source
    media="(max-width: 1024px)"
    srcset="
      https://demo.sirv.com/product.jpg?w=600&h=400&scale.option=fill 600w,
      https://demo.sirv.com/product.jpg?w=1200&h=800&scale.option=fill 1200w
    "
    sizes="50vw"
  />
  <img
    src="https://demo.sirv.com/product.jpg?w=800"
    srcset="
      https://demo.sirv.com/product.jpg?w=600 600w,
      https://demo.sirv.com/product.jpg?w=1200 1200w,
      https://demo.sirv.com/product.jpg?w=1800 1800w
    "
    sizes="33vw"
    alt="Product"
    width="800"
    height="600"
  />
</picture>

Step 5: Preventing Layout Shift (CLS)

Responsive images cause Cumulative Layout Shift (CLS) when the browser doesn’t know the aspect ratio before the image loads. The fix is simple. Always include width and height attributes:

<img
  src="photo.jpg?w=800"
  srcset="photo.jpg?w=400 400w, photo.jpg?w=800 800w"
  sizes="(max-width: 640px) 100vw, 50vw"
  alt="Product"
  width="800"
  height="600"
/>

Modern browsers use the width/height ratio to reserve space before anything loads. Pair it with this CSS:

img {
  max-width: 100%;
  height: auto;
}

That keeps images fluid while maintaining aspect ratio and preventing layout shifts.

When the aspect ratio changes between breakpoints (art direction), use the CSS aspect-ratio property:

.hero-image {
  aspect-ratio: 16 / 9;
  width: 100%;
  object-fit: cover;
}

@media (max-width: 640px) {
  .hero-image {
    aspect-ratio: 1 / 1;
  }
}

Step 6: Lazy Loading Gotchas

Be careful combining responsive images with lazy loading:

<!-- GOOD: Lazy load below-the-fold images -->
<img
  src="product.jpg?w=800"
  srcset="product.jpg?w=400 400w, product.jpg?w=800 800w"
  sizes="50vw"
  loading="lazy"
  alt="Product below the fold"
  width="800"
  height="600"
/>

<!-- GOOD: Don't lazy load the hero/LCP image -->
<img
  src="hero.jpg?w=1200"
  srcset="hero.jpg?w=800 800w, hero.jpg?w=1200 1200w, hero.jpg?w=1600 1600w"
  sizes="100vw"
  loading="eager"
  fetchpriority="high"
  alt="Hero image"
  width="1200"
  height="600"
/>

Your LCP image (usually the hero) needs loading="eager" (or just omit the attribute, since eager is the default) plus fetchpriority="high" to tell the browser to prioritize it.

Also consider preloading the hero with a <link> tag in the <head>:

<link
  rel="preload"
  as="image"
  href="hero.jpg?w=1200"
  imagesrcset="hero.jpg?w=800 800w, hero.jpg?w=1200 1200w, hero.jpg?w=1600 1600w"
  imagesizes="100vw"
/>

Note: imagesrcset and imagesizes are the preload equivalents of srcset and sizes.

Step 7: The CDN Advantage

Without a CDN, responsive images means maintaining multiple files per image. Resized versions at each breakpoint, in each format. For a product catalog of 1,000 images with 5 size variants and 3 formats, that’s 15,000 files. Nobody wants to manage that.

With an image CDN like Sirv, you upload one master image and generate variants through URL parameters:

/product.jpg?w=400   → 400px wide
/product.jpg?w=800   → 800px wide
/product.jpg?w=1200  → 1200px wide

Format conversion happens automatically. Sirv checks the browser’s Accept header and serves AVIF, WebP, or JPEG accordingly. No <picture> element needed for format switching.

Sirv’s automatic approach

Want the simplest possible implementation? Sirv’s JavaScript handles everything:

<img class="Sirv" data-src="https://your-account.sirv.com/product.jpg" alt="Product">
<script src="https://scripts.sirv.com/sirvjs/v3/sirv.js"></script>

This detects the image’s rendered size, requests the right dimensions from the CDN, serves the best format, adds lazy loading, and handles retina displays. All without writing srcset or sizes.

The trade-off: you give up fine-grained control over sizes and breakpoints in exchange for zero-config responsive images.

Step 8: Testing and Debugging

Chrome DevTools

  1. Open DevTools and go to the Network panel
  2. Filter by “Img” type
  3. Resize the browser window and reload
  4. Check which srcset candidate was downloaded (look at the “Size” column)
  5. Toggle device emulation (phone, tablet) to test different DPRs

Verify the right image loaded

In the Network panel, hover over the image URL to see the dimensions. Compare against your srcset candidates to confirm the browser chose correctly.

Common debugging issues

ProblemLikely CauseFix
Always loads the largest imageMissing sizes attribute (defaults to 100vw)Add sizes matching your CSS layout
Image looks blurrysrcset candidates too small for the DPRAdd larger width candidates
CLS in LighthouseMissing width/height on <img>Add explicit width and height attributes
srcset seems ignoredsrc URL matches a srcset URLEnsure src is a distinct fallback
Wrong candidate chosensizes doesn’t match actual CSSInspect rendered width and adjust sizes

Lighthouse audit

Run a Lighthouse performance audit and look for:

  • “Properly size images”: flags images larger than their rendered size
  • “Serve images in modern formats”: flags JPEG/PNG that could be WebP/AVIF
  • “Image elements do not have explicit width and height”: CLS warning

Quick Reference Checklist

  • Add srcset with 4-6 width candidates matching your layout
  • Include sizes that reflects your CSS (don’t default to 100vw)
  • Set width and height on all <img> tags for CLS prevention
  • Use loading="lazy" on below-the-fold images only
  • Add fetchpriority="high" to your LCP image
  • Preload the hero image in <head> for fastest LCP
  • Use <picture> only for art direction (different crops), not just format switching
  • Test across viewports in DevTools to verify correct candidate selection

Related Resources

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial