A Developer’s Guide to Responsive Images
A 2400-pixel-wide hero image looks stunning on a 27-inch desktop monitor. On a phone screen over a cellular connection, that same image wastes bandwidth, drains battery, and slows page load to a crawl. The browser downloads every byte of a file it does not need, then scales it down to fit a viewport one-third the size.
This is the problem responsive images solve. By giving the browser a set of image candidates and the information it needs to choose the right one, you can serve sharp, appropriately sized images to every device without sacrificing visual quality on any of them.
If you are new to image optimization in general, start there for foundational concepts. This guide focuses specifically on the HTML techniques and tooling for delivering the correct image dimensions across viewports.
Why Responsive Images Matter
Images remain the heaviest resource on most web pages. The HTTP Archive reports that images account for roughly 40-50% of total page weight on the median site. When a mobile visitor downloads a desktop-sized image, the waste compounds in three ways:
- Bandwidth cost. A 1.2 MB hero image that could be 200 KB at mobile dimensions wastes a megabyte of transfer on every page view.
- Rendering delay. Larger files take longer to decode. On lower-powered mobile processors, decoding a 4000×2600 JPEG can block the main thread for hundreds of milliseconds.
- Core Web Vitals impact. Largest Contentful Paint (LCP) is directly tied to how fast the main image loads. Serving oversized images is one of the most common reasons for poor LCP scores. See our guide to Core Web Vitals and image performance for a deeper look at this relationship.
Responsive images address all three problems by letting the browser select the smallest file that still looks sharp at the rendered size.
Resolution Switching with srcset and sizes
The most common responsive image use case is resolution switching — serving the same image at different dimensions depending on the viewport width and display density.
The srcset Attribute
The srcset attribute provides the browser with a list of image files and their intrinsic widths. The browser uses this information, along with the current viewport size and device pixel ratio, to select the best candidate.
<img
src="hero-800.jpg"
srcset="
hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w,
hero-2400.jpg 2400w
"
sizes="100vw"
alt="Mountain landscape at sunrise"
width="1200"
height="600"
>
Each entry in srcset pairs a URL with a width descriptor (400w, 800w, etc.) that tells the browser the intrinsic pixel width of that file. The src attribute serves as a fallback for browsers that do not support srcset.
The sizes Attribute
The sizes attribute tells the browser how wide the image will be rendered in CSS pixels at different viewport widths. Without it, the browser assumes the image occupies the full viewport width (100vw), which leads to poor choices when images are displayed in a grid or sidebar.
<img
src="product-800.jpg"
srcset="
product-400.jpg 400w,
product-800.jpg 800w,
product-1200.jpg 1200w
"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
alt="Wireless headphones in black"
width="800"
height="800"
>
This tells the browser: on viewports up to 640px, the image fills the full width. Between 640px and 1024px, it takes half the viewport. Above 1024px, it occupies one-third. The browser combines this information with the device pixel ratio to pick the right file. A 2x Retina phone at 375px viewport width needs an image at least 750 pixels wide, so it would select product-800.jpg.
Always Include width and height
Explicit width and height attributes (or equivalent CSS aspect-ratio) prevent layout shift as the image loads. The browser can reserve the correct space before it downloads the file, which directly improves Cumulative Layout Shift (CLS) scores.
Art Direction with the picture Element
Resolution switching handles the common case where you serve the same composition at different sizes. Art direction is different — it lets you serve entirely different image crops or compositions based on the viewport.
Consider a wide banner image that includes a person on the left and text on the right. On mobile, a scaled-down version of the full banner becomes unreadable. Art direction lets you serve a tightly cropped portrait shot on narrow screens instead.
<picture>
<source
media="(min-width: 1024px)"
srcset="banner-wide-1600.jpg 1600w,
banner-wide-2400.jpg 2400w"
sizes="100vw"
>
<source
media="(min-width: 640px)"
srcset="banner-medium-800.jpg 800w,
banner-medium-1200.jpg 1200w"
sizes="100vw"
>
<img
src="banner-mobile-600.jpg"
srcset="banner-mobile-400.jpg 400w,
banner-mobile-600.jpg 600w"
sizes="100vw"
alt="Team member working at a desk"
width="600"
height="800"
>
</picture>
The browser evaluates <source> elements in order and uses the first one whose media query matches. The <img> element at the bottom is required and acts as the default fallback.
Combining Art Direction with Format Selection
The <picture> element is also the standard way to serve modern formats like WebP or AVIF with fallbacks for older browsers:
<picture>
<source
type="image/avif"
srcset="hero-800.avif 800w,
hero-1200.avif 1200w,
hero-1600.avif 1600w"
sizes="100vw"
>
<source
type="image/webp"
srcset="hero-800.webp 800w,
hero-1200.webp 1200w,
hero-1600.webp 1600w"
sizes="100vw"
>
<img
src="hero-800.jpg"
srcset="hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w"
sizes="100vw"
alt="Product showcase"
width="1600"
height="900"
>
</picture>
The browser picks the first supported format and then selects the best size within that format. This gives you the combined benefits of modern compression and responsive sizing.
Generating Multiple Image Sizes
Writing the HTML markup is the straightforward part. The real challenge is producing all the image variants. A single source image might need five widths across three formats, resulting in fifteen files. Multiply that by the number of images on your site, and manual resizing becomes impractical.
Build-Time Tools
For static sites and build pipelines, several tools automate variant generation:
- Sharp (Node.js) — High-performance image processing library. Works well in Vite, Webpack, or custom build scripts.
- ImageMagick / libvips — Command-line tools that can batch-resize and convert formats in CI pipelines.
- Responsive image plugins — Most static site generators and frameworks (Next.js, Nuxt, Gatsby, Astro) include built-in image components that generate variants at build time.
API-Based Processing
For dynamic content where images are uploaded by users or pulled from a CMS, build-time processing is not an option. An image optimization API handles resizing and compression on the fly.
The MegaOptim API accepts an image and returns optimized versions at the compression level you specify. You can integrate it into your upload pipeline to generate multiple sizes when content is created, then store the variants and reference them in your srcset markup. This approach offloads the CPU-intensive work of encoding to a dedicated service while keeping your application servers focused on serving requests.
For WordPress sites, the MegaOptim plugin handles this automatically by optimizing images at every registered thumbnail size when they are uploaded to the media library.
Choosing Your Breakpoints
A common question is which widths to generate. A practical approach:
- Start with your design breakpoints and the rendered widths at each breakpoint.
- Account for 2x display density (multiply each rendered width by two).
- Cap the maximum at 2400-2600 pixels, beyond which the quality gains are negligible.
- Aim for roughly 200-400 pixel increments between sizes.
A typical set for a full-width hero image might be: 400, 800, 1200, 1600, 2400.
A Complete Real-World Example
Here is a full implementation for a blog post featured image that uses modern formats, art direction for mobile cropping, and responsive sizing:
<picture>
<!-- Desktop: full-width landscape crop, AVIF -->
<source
media="(min-width: 768px)"
type="image/avif"
srcset="featured-800.avif 800w,
featured-1200.avif 1200w,
featured-1600.avif 1600w,
featured-2400.avif 2400w"
sizes="(max-width: 1200px) 100vw, 1200px"
>
<!-- Desktop: full-width landscape crop, WebP fallback -->
<source
media="(min-width: 768px)"
type="image/webp"
srcset="featured-800.webp 800w,
featured-1200.webp 1200w,
featured-1600.webp 1600w,
featured-2400.webp 2400w"
sizes="(max-width: 1200px) 100vw, 1200px"
>
<!-- Mobile: tighter crop, AVIF -->
<source
type="image/avif"
srcset="featured-mobile-400.avif 400w,
featured-mobile-800.avif 800w"
sizes="100vw"
>
<!-- Mobile: tighter crop, WebP -->
<source
type="image/webp"
srcset="featured-mobile-400.webp 400w,
featured-mobile-800.webp 800w"
sizes="100vw"
>
<!-- Ultimate fallback: JPEG -->
<img
src="featured-800.jpg"
srcset="featured-800.jpg 800w,
featured-1200.jpg 1200w"
sizes="(max-width: 1200px) 100vw, 1200px"
alt="Developer working on responsive design at a desk"
width="1200"
height="675"
loading="lazy"
decoding="async"
>
</picture>
Note the loading="lazy" and decoding="async" attributes on the <img> element. Use loading="lazy" for images below the fold to defer their download until the user scrolls near them. Do not lazy-load your LCP image — the hero or featured image above the fold should load eagerly (the default behavior).
Testing Responsive Images in DevTools
Chrome DevTools provides the best tooling for verifying your responsive image implementation:
-
Device toolbar. Toggle the device toolbar (Ctrl+Shift+M) to simulate different viewport widths and device pixel ratios. Reload the page after changing the viewport to trigger a fresh image selection.
-
Network panel. Filter by “Img” to see which image file the browser actually downloaded. Compare the file size at different viewport widths to confirm that smaller variants are being served on narrower screens.
-
Elements panel. Inspect the
<img>element and check itscurrentSrcproperty in the console ($0.currentSrc) to see which source the browser selected. -
Lighthouse. Run a Lighthouse audit with “Performance” checked. The audit flags images that are significantly larger than their rendered dimensions under the “Properly size images” diagnostic.
-
Rendering tab. Enable “Core Web Vitals” overlay to see the LCP element highlighted in real time. Confirm your hero image is loading at the right size and being identified as the LCP element.
Summary
Responsive images are not optional for production websites. Serving a single image size forces mobile users to download files many times larger than necessary, directly harming load times, bandwidth costs, and Core Web Vitals scores.
The implementation comes down to two patterns: use srcset and sizes for resolution switching when the same composition works at all viewports, and use the <picture> element when you need art direction or format fallbacks. Combine both for the best results.
The markup itself is simple. The harder part — generating all the image variants — is where tooling matters. Whether you use build-time processing, a framework’s built-in image component, or an API like MegaOptim, automate the variant generation so it stays consistent and maintainable as your site grows.