Responsive Images with srcset and sizes: Serve the Right Size

Crawvix Editorial Team · Published October 29, 2026 · Last updated October 29, 2026 · 11 min read

A single 2,000-pixel-wide photo works on a big monitor. On a phone with a 390-pixel-wide column, the same file sends many times more pixels than the screen can show. The visitor pays for those pixels in data and waiting time, and Lighthouse reports the waste as an image that is larger than it needs to be.

Responsive images solve this in HTML. You provide several sizes of the same picture, and the browser picks the one that fits the space and the screen. This guide explains how responsive images with srcset work, how to write the sizes attribute without guessing, when to use <picture> instead, and how to check in the browser that the right file was downloaded.

It is for site owners and developers who can edit page templates, or who use a CMS that generates multiple image sizes. You will finish with correct markup for a typical content image and a hero image, and a method for testing any other image on your site.

Before you begin

You need to be able to produce several widths of each image, either through your CMS's media settings, an image processing step in your build, or an image CDN that resizes on request. You also need to know how wide each image is displayed in your layout at each screen width. Look at your CSS or measure in browser developer tools.

Back up the templates you edit. Start with the images that matter most: the first large image on your key templates and the images that Lighthouse lists under its image-size opportunity. Allow an hour or two for a first template.

Set expectations. Smaller image files reduce transfer, but they do not promise a score or a ranking, and a blurry image is a worse result than a heavy one. Check quality on a real phone.

How the browser chooses an image

MDN describes two different problems. Resolution switching serves the same picture at different pixel sizes or densities, using srcset and sizes. Art direction serves differently cropped pictures for different layouts, using <picture>. Most images on most sites need only the first.

Here is a content image using resolution switching:

<img
  srcset="photo-480.jpg 480w,
          photo-800.jpg 800w,
          photo-1200.jpg 1200w"
  sizes="(width <= 600px) 100vw,
         (width <= 1000px) 50vw,
         400px"
  src="photo-800.jpg"
  width="800" height="533"
  alt="Two engineers reviewing a server rack">

The pieces work like this.

  • srcset is the list of candidates. With width descriptors, each entry is a file name plus the file's real width in pixels, written with w, not px. MDN points out that unit explicitly.
  • sizes tells the browser how wide the image will be displayed, before the CSS has loaded. Each entry is a media condition followed by the slot width. MDN says you can use any length value, such as 50vw or 400px, but not a percentage. The last entry has no condition and is the default. The browser uses the first condition that matches and ignores the rest, so the order matters.
  • src is the fallback. Browsers that do not understand srcset ignore it and load this file.

The browser looks at the screen size, pixel density, zoom level and, according to MDN, network speed. It works out the slot width from sizes, then picks the smallest listed image that is big enough, scaling down from the next larger one when there is no exact match.

web.dev adds that sizes is required when you use width descriptors, and lists using width descriptors without sizes as a common pitfall. It also says to use either width or density descriptors, not both.

Why does HTML need to carry this, instead of CSS or JavaScript choosing the image? MDN explains that browsers start loading images before they finish parsing CSS and scripts. If a script waited to detect the viewport and then swapped the file, the first image would already have downloaded and you would load two.

Step-by-step instructions

Step 1: Find images that are too big

Run Lighthouse on a mobile profile and open the image sizing opportunity. The Chrome documentation says the audit compares each image's rendered size with the file's actual size, taking device pixel ratio into account, and flags an image when the rendered size is at least 4 KiB smaller than the actual size. As of Lighthouse 13, the same documentation says this audit has moved into the "Improve image delivery" insight, so your report may use that name.

You can also use the Network panel: select an image request, compare its natural size with the size shown on the page (hover over the element in the Elements panel to see rendered and intrinsic size).

Make a list of the offenders, ordered by file size and by how often the template is visited.

What success looks like: You have a list of images, the displayed width of each at phone, tablet and desktop, and the current file width.

Step 2: Generate several widths

Choose a small set of widths that cover your layout, for example 480, 800 and 1200 pixels, plus larger ones if full-width images need them. Do not create dozens of versions; a handful covers most cases.

Options for producing them:

  • Many CMSs create several sizes on upload. For WordPress, the Chrome documentation advises inserting images from the media library at appropriate sizes and avoiding "Full Size" unless its dimensions suit the layout.
  • A build step or script can resize images to your widths.
  • An image CDN can return a resized version from a parameter in the URL. The Chrome documentation lists image CDNs as one option.

Name files so the width is visible (photo-480.jpg), and record the real pixel width of each, since srcset needs it.

What success looks like: Each key image exists in at least three widths, with a known pixel width.

Step 3: Write srcset and sizes from your layout

Look at the CSS. If a content image fills the column on phones, is half the width in a two-column grid on tablets, and has a fixed 400 pixel slot on desktop, say exactly that in sizes, as in the example above. If the image is always full-width, sizes="100vw" is the honest answer.

Two checks avoid the usual errors.

  1. Make sure the page has <meta name="viewport" content="width=device-width, initial-scale=1">. MDN notes this is needed so mobile browsers use their real viewport width.
  2. Do not leave out sizes when you use w descriptors. Without it, the browser has to assume a slot width and may download an image that is far too large.

When an image has a fixed CSS size, use density descriptors instead. Here the CSS sets a 320 pixel width:

<img
  srcset="logo-320.png, logo-480.png 1.5x, logo-640.png 2x"
  src="logo-640.png"
  width="320" height="107"
  alt="Example Co logo">

MDN explains that sizes is not needed with x descriptors, because the browser works out the display density, and that 1x is implied. A screen with two device pixels per CSS pixel loads the 640 pixel file. For simple logos and icons, SVG is often a better answer than any raster size, as both MDN and the Chrome documentation suggest vector formats where the artwork allows.

What success looks like: Each srcset entry has the true pixel width, sizes matches the CSS layout, and the viewport meta tag is present.

Step 4: Use picture for different crops or formats

Use <picture> in two cases.

Art direction. A wide banner may shrink until the subject is tiny on a phone. MDN's example serves a close crop to narrow screens:

<picture>
  <source media="(width < 800px)" srcset="team-close-480.jpg">
  <source media="(width >= 800px)" srcset="team-wide-1200.jpg">
  <img src="team-wide-1200.jpg" width="1200" height="600"
       alt="The support team at their desks">
</picture>

The first <source> whose media condition is true is used, and the <img> is required: MDN says that without it no image appears, and it carries src and alt. MDN also says to use media only for art direction, and not to also put media conditions in sizes in that case.

Format choice. The type attribute lets the browser skip formats it cannot decode:

<picture>
  <source srcset="photo-800.avif" type="image/avif">
  <source srcset="photo-800.webp" type="image/webp">
  <img src="photo-800.jpg" width="800" height="533" alt="Two engineers reviewing a server rack">
</picture>

MDN says that if the browser does not support the given type, it skips that source, and the <img> src is the final fallback. List the most preferred format first. You can combine type with srcset and sizes on a <source>. Our guide on WebP and AVIF covers when each format makes sense.

What success looks like: Phones receive the crop meant for them, and the format fallback order is most-preferred first.

Step 5: Set dimensions and decide how each image loads

Always include width and height on the image. web.dev lists this among its responsive image tips, because it reserves space and avoids layout shift. Pair it with CSS such as max-inline-size: 100% and block-size: auto so the image still scales. See Cumulative Layout Shift for the reasoning.

Then pick the loading behaviour. web.dev recommends loading="lazy" for images below the fold and loading="eager" for hero images. A lazy-loaded main image can slow Largest Contentful Paint, so leave it eager. web.dev also cautions against overusing fetchpriority="high": set it only on an image that is truly vital. Our guide on lazy loading images without hiding them from search goes further, and fixing Largest Contentful Paint explains how to find the LCP image.

web.dev adds one more pitfall: you cannot preload images in different formats based on browser support, so do not rely on a preload to choose between AVIF and JPEG.

What success looks like: Every image has dimensions, the hero loads eagerly, and lower images are lazy.

Step 6: Verify in the browser

Open the page with the Network panel filtered to images and clear the cache. Load it at a phone width, then at a desktop width. Read which file was requested for each image. In the Elements panel, selecting the <img> shows the browser's current choice as currentSrc in the console ($0.currentSrc after selecting the element).

Test with throttled mobile settings and also at a high-density screen setting, because the density changes the selection. Then re-run Lighthouse and compare the image opportunity before and after. Browsers may choose a larger file than you expect, since they consider more than the viewport width. Judge by the pattern across several widths.

What success looks like: The phone request is a smaller file than the desktop one, and the image-size opportunity shrinks or disappears.

Final verification checklist

  • Large images on key templates exist in several widths.
  • Every srcset using w descriptors also has a sizes attribute.
  • Each w value is the real pixel width of its file.
  • The viewport meta tag is on the page.
  • Art-directed images use <picture> with a required <img> carrying src and alt.
  • <source type="..."> entries list the preferred format first, with a JPEG or PNG fallback.
  • Every image has width and height.
  • The main image is not lazy-loaded; images below the fold are.
  • A phone-width load downloads a smaller file than a desktop load.
  • The image-size opportunity in Lighthouse is smaller or gone, and pictures still look sharp.

Common mistakes to avoid

  • Using w descriptors with no sizes. The browser guesses the slot width. Write sizes to match the layout.
  • Putting percentages in sizes. MDN says percentages are not allowed there. Use vw or fixed lengths.
  • Misordered conditions. The browser stops at the first match. Put the narrowest condition first and the default last.
  • Wrong w values. If the number you list differs from the real file width, the browser picks wrongly. Read the width from the file.
  • Mixing x and w descriptors. Use one kind in each srcset.
  • Omitting the <img> inside <picture>. No image shows. Always include it with src and alt.
  • Lazy-loading the hero image. It delays the largest element on the page. Leave it eager.
  • Making a huge set of sizes. More files means more to generate, store and maintain for little gain. A few well-chosen widths work.

Frequently asked questions

Do I need sizes every time?

You need it with w descriptors. With x descriptors (1x, 2x) you do not. web.dev says to use one kind or the other in a given srcset.

Why did the browser download a bigger image than expected?

The browser decides, and MDN says it considers screen size, pixel density, zoom, orientation and network speed. A high-density phone legitimately takes a larger file for the same slot.

Should I use CSS background-image instead?

Background images do not take part in srcset. web.dev mentions image-set() for CSS backgrounds, where the browser chooses by pixel density. For content that needs alt text, use <img>.

Is srcset an SEO feature?

It is a performance and user experience feature. It helps phones avoid downloading oversized files. It does not promise a ranking change, and your images still need meaningful alt text and crawlable URLs.

What about SVG?

For logos and icons that scale, SVG avoids needing multiple sizes. MDN notes it is not suited to photographs.

Official references


Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 25 Performance guides