Responsive Images: srcset, sizes & picture Element Guide

Images account for roughly half of the average web page's total weight. On a responsive site, serving a single fixed-size image to every device means either wasting bandwidth on mobile or showing a blurry image on desktop. Responsive images solve this by letting the browser select the optimal image source based on screen size, pixel density, and supported formats.

This guide covers the core responsive image techniques — srcset, the sizes attribute, and the <picture> element — along with lazy loading, image CDNs, and practical workflows for real-world projects.

The Fixed-Size Problem

Consider a standard <img> tag pointing to a single 1600×900 JPEG. On a desktop with a large viewport, the image looks sharp. But on a 375px-wide phone, the browser downloads the full 1600px file, then shrinks it to fit — wasting up to 80% of the downloaded pixels. On a 4K display, the same image might look soft because it doesn't have enough pixels for the high-density screen.

The numbers are dramatic. A 1600px JPEG might be 400 KB. The phone only needs a 400px version that's 35 KB. That's 365 KB of wasted data — per image, per page load, per user. Multiply by a dozen images and you're sending megabytes of unnecessary data to mobile users on slow connections.

Responsive images fix this by providing multiple source files and letting the browser pick the right one. The result: faster loads, lower bandwidth costs, better Core Web Vitals, and sharp images on every screen.

srcset — Resolution Switching

The srcset attribute on an <img> tag provides a set of image sources at different resolutions. The browser evaluates the user's viewport width and pixel density, then selects the most appropriate source.

Width Descriptors (w)

Width descriptors tell the browser the intrinsic width of each image file in pixels:

<img
  src="hero-800.jpg"
  srcset="hero-400.jpg 400w,
          hero-800.jpg 800w,
          hero-1200.jpg 1200w,
          hero-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1200px) 50vw,
         800px"
  alt="Mountain landscape at sunset"
  width="1600" height="900"
  loading="lazy"
>

The w descriptor (e.g., 400w) specifies the image's actual pixel width. The browser combines this with the sizes attribute to calculate which source is the best match. The src attribute serves as a fallback for browsers that don't support srcset.

Pixel Density Descriptors (x)

For fixed-size images (like logos or icons), use density descriptors instead:

<img
  src="logo-200.png"
  srcset="logo-200.png 1x,
          logo-400.png 2x,
          logo-600.png 3x"
  alt="Company logo"
  width="200" height="50"
>

Here, 1x is the standard display, 2x is for Retina/HiDPI screens, and 3x is for ultra-high-density mobile displays. The browser picks based on window.devicePixelRatio. Density descriptors don't require a sizes attribute.

The sizes Attribute

When using width descriptors (w), the sizes attribute tells the browser how wide the image will be rendered at different viewport widths. Without sizes, the browser assumes the image is 100vw — full viewport width — which usually results in downloading an image that's far too large.

sizes="(max-width: 600px) 100vw,
       (max-width: 1200px) 50vw,
       800px"

This reads as: "On viewports up to 600px, the image fills 100% of the viewport width. On viewports up to 1200px, the image fills 50%. On anything larger, the image is 800px wide." The browser uses this information — combined with the device pixel ratio — to pick the best source from srcset.

Getting sizes right is critical for performance. An inaccurate sizes value can cause the browser to download a 1600px image when a 400px one would suffice. Use your browser's DevTools to verify which source is being selected.

Common sizes Patterns

  • Full-width hero: sizes="100vw"
  • Two-column grid: sizes="(max-width: 768px) 100vw, 50vw"
  • Three-column grid: sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  • Fixed sidebar image: sizes="(max-width: 768px) 100vw, 300px"
  • With max-width container: sizes="(max-width: 1200px) 100vw, 1200px"

The picture Element — Art Direction

The <picture> element gives you complete control over which image source is served. Unlike srcset (where the browser makes the final choice), <picture> uses media queries to deterministically select a source. This is essential for art direction — showing a differently cropped image on mobile vs desktop.

Art Direction

<picture>
  <source media="(max-width: 600px)"
          srcset="hero-mobile-400.jpg 400w,
                  hero-mobile-800.jpg 800w"
          sizes="100vw">
  <source media="(max-width: 1200px)"
          srcset="hero-tablet-800.jpg 800w,
                  hero-tablet-1200.jpg 1200w"
          sizes="100vw">
  <img src="hero-desktop-1600.jpg"
       srcset="hero-desktop-1200.jpg 1200w,
               hero-desktop-1600.jpg 1600w"
       sizes="100vw"
       alt="Team meeting in modern office"
       width="1600" height="900">
</picture>

In this example, mobile users see a tightly cropped portrait photo (showing just one person), tablet users see a medium crop, and desktop users see the full wide shot. Each version is optimized for its context. Without <picture>, you'd have to serve the same wide crop everywhere — wasting mobile screen space on a tiny, hard-to-see image.

Format Switching

The <picture> element also enables format negotiation — serving modern formats to browsers that support them with a fallback for older browsers:

<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Product photo" width="800" height="600">
</picture>

The browser checks each <source> in order and uses the first one it supports. AVIF-capable browsers get the smallest file; WebP-capable browsers get the next best; everyone else gets the JPEG fallback. This technique typically reduces image payload by 30–60% compared to JPEG-only delivery.

Combining Art Direction and Format Switching

You can combine both techniques in a single <picture> element by including media and type attributes together. List format sources within each media condition:

<picture>
  <source media="(max-width: 600px)"
          type="image/avif"
          srcset="hero-mobile.avif">
  <source media="(max-width: 600px)"
          type="image/webp"
          srcset="hero-mobile.webp">
  <source media="(max-width: 600px)"
          srcset="hero-mobile.jpg">
  <source type="image/avif" srcset="hero-desktop.avif">
  <source type="image/webp" srcset="hero-desktop.webp">
  <img src="hero-desktop.jpg" alt="Hero image"
       width="1600" height="900">
</picture>

Lazy Loading

Native lazy loading defers off-screen images until the user scrolls near them, saving bandwidth and speeding up initial page load:

<img src="photo.jpg" alt="..." loading="lazy" width="800" height="600">

The loading="lazy" attribute works with both standard <img> tags and images inside <picture> elements. The browser handles all the intersection logic — no JavaScript library needed.

Critical rule: Never lazy-load above-the-fold images. Your hero image, header logo, and any content visible without scrolling should load immediately. Lazy loading these elements delays LCP and hurts Core Web Vitals. Use loading="eager" (or simply omit the attribute) for above-the-fold content, and add fetchpriority="high" to the LCP image for an extra speed boost:

<img src="hero.jpg" alt="..." loading="eager" fetchpriority="high"
     width="1600" height="900">

Image CDNs and Optimization Services

Image CDNs automate responsive image delivery by generating multiple sizes and formats on the fly from a single source image. Services like Cloudinary, Imgix, Cloudflare Images, and Vercel Image Optimization accept URL parameters to control width, height, format, and quality:

<img
  src="https://cdn.example.com/photo.jpg?w=800&q=80&f=auto"
  srcset="https://cdn.example.com/photo.jpg?w=400&q=80&f=auto 400w,
          https://cdn.example.com/photo.jpg?w=800&q=80&f=auto 800w,
          https://cdn.example.com/photo.jpg?w=1200&q=80&f=auto 1200w"
  sizes="(max-width: 768px) 100vw, 50vw"
  alt="Product showcase"
>

The f=auto parameter tells the CDN to negotiate the format based on the browser's Accept header — serving AVIF to Chrome, WebP to Safari, and JPEG elsewhere. This eliminates the need for <picture> element format switching entirely.

Image CDNs are the most practical solution for sites with hundreds or thousands of images. They remove the build-step burden of generating multiple sizes and formats manually.

Practical Workflow

Here's a step-by-step workflow for implementing responsive images on a real project:

  1. Audit your images: Identify which images are large, which are above the fold (LCP candidates), and which are decorative. Focus optimization effort on the biggest and most visible images first.
  2. Choose your breakpoints: Decide on 3–4 image widths that match your layout breakpoints. Typical widths: 400, 800, 1200, 1600 pixels. You don't need a separate file for every possible viewport width.
  3. Generate resized versions: Use a build tool (Sharp, ImageMagick, Squoosh CLI) or an image CDN to create each size in JPEG, WebP, and optionally AVIF formats.
  4. Write the markup: Use srcset with width descriptors and accurate sizes values. For art-directed images, use <picture>. For format switching without a CDN, use <picture> with type attributes.
  5. Add dimensions: Always include width and height attributes on every <img> to prevent layout shifts (CLS).
  6. Apply lazy loading: Add loading="lazy" to all below-the-fold images. Add fetchpriority="high" to the LCP image.
  7. Test and measure: Run Lighthouse, check the Network tab to verify the correct source is being selected at each viewport width, and validate CLS and LCP scores.

Performance Impact

Responsive images have a direct and measurable impact on Core Web Vitals:

  • Largest Contentful Paint (LCP): The hero image is the LCP element on most pages. Serving a correctly sized image can cut LCP by 1–3 seconds on mobile. Adding fetchpriority="high" and preloading the LCP image further reduces the time.
  • Cumulative Layout Shift (CLS): Including width and height attributes (or using CSS aspect-ratio) lets the browser reserve space before the image loads, eliminating layout shifts.
  • Bandwidth savings: Properly implemented responsive images reduce total image payload by 40–70% compared to serving a single large file. This translates to faster loads, lower data costs for users, and reduced CDN/hosting bandwidth bills.
  • Time to Interactive (TTI): Fewer bytes to download means the main thread is free sooner, improving interactivity — especially on low-powered mobile devices with slow connections.

The investment in responsive image markup pays for itself many times over in performance, user experience, and search rankings.

Frequently Asked Questions

srcset on an img tag lets the browser choose the best resolution of the same image (resolution switching). The picture element lets you serve entirely different images based on media conditions like viewport width or supported formats (art direction and format switching). Use srcset when you have the same image at multiple resolutions. Use picture when you need to crop differently for mobile vs desktop, or serve WebP/AVIF with a fallback.
If your srcset uses width descriptors (e.g., 800w, 1200w), yes — you need the sizes attribute to tell the browser how wide the image will be rendered. Without sizes, the browser assumes the image is 100vw (full viewport width), which leads to downloading images that are far too large. If your srcset uses pixel density descriptors (1x, 2x), you don't need sizes because the browser already knows the display density.
Add loading="lazy" to any img tag (including those with srcset) or to the img inside a picture element. The browser will defer loading the image until it's near the viewport. Do not lazy-load above-the-fold images like hero banners — those should load immediately (loading="eager" or just omit the attribute) to avoid hurting Largest Contentful Paint (LCP). Native lazy loading is supported in all modern browsers.
AVIF offers the best compression (50–70% smaller than JPEG) but encoding is slower. WebP is 25–35% smaller than JPEG with fast encoding and universal browser support. Use the picture element to serve AVIF first, WebP second, and JPEG/PNG as the final fallback. For photos use AVIF/WebP/JPEG; for graphics and icons use SVG or WebP/PNG. All modern browsers support WebP; AVIF support is above 90% globally.
Responsive images directly impact Largest Contentful Paint (LCP) — the hero image is often the LCP element. Serving correctly sized images reduces download time and speeds up LCP. They also reduce Cumulative Layout Shift (CLS) when you include width and height attributes so the browser reserves space. Properly sized images save bandwidth, which is especially impactful on mobile connections where oversized images can add seconds to load time.

Resize Images for Every Breakpoint

Generate perfectly sized images for every viewport with our free Image Resizer — batch resize, crop, and export in seconds.

Explore All Tools →