When speaking about responsive images, we usually focus on CSS techniques that make an image adapt to different viewport sizes, such as setting the
max-width property to 100 percent. However, using solely CSS to make images responsive won’t improve performance and page load times, as you’re still serving the same size image for all devices. For instance, loading a 2000px image on mobile comes with a huge (and unnecessary) overhead.
Luckily, HTML also has its own syntax, elements, and attributes for responsive images that let you serve different images for different viewport sizes, resolutions, and other conditions. In this guide, we’ll look into how to add responsive images in HTML and discuss the following features:
The standard image syntax
To add responsive images in HTML, your starting point is always the standard image syntax, consisting of the
<img> element and the
<img src="images/myimage.jpg alt="my image">
src attribute, you can use either an absolute (starting with the
https:// protocol) or a relative path — I’ve used the latter above.
You always need to “build” the responsive image syntax on top of this standard
<img> definition; this also ensures backward compatibility.
srcset attribute is an optional attribute of image-related HTML elements, including the
<img> tag. You can use it to assign different image sources to certain features of the user’s device, such as viewport size or pixel density. The user’s browser will only load the image that’s the most suitable for the user’s device — which can mean a significant performance gain.
When using the
srcset attribute, you’re supposed to add the same image in different sizes. This is because this attribute only gives hints to the user’s browser that it can still load a different image as it also considers other things, such as the bandwidth of the network. If you use
srcset, the browser will assume that all of the image sources are visually identical. If you want to serve different-looking images, you’ll need to use the
<source> elements — we’ll look into them later on.
There are two ways to serve different size images with the
- you can specify different image sources based on the pixel density of the user’s device
- you can serve different images for low-resolution vs. high-resolution devices
wdescriptors and the
- the term “
wdescriptor” stands for “width descriptor”
- you can specify different image sources based on the width of the images
- the browser considers both pixel density and layout dimensions (the space it needs to allocate for the image)
- the term “
Now, let’s have a look at the respective syntaxes.
srcset attribute with
Using the following HTML, you can serve images for both low-resolution and high-resolution displays:
<img src="images/myimage.jpg" srcset="images/myimage-2x.jpg 2x" alt="my image">
The second image,
myimage-2x.jpg is twice as big as the default one (e.g. 1280x960px compared to 640x480px), but it will only be loaded on high-resolution screens. It will be the user’s browser that decides which image to serve, mostly based on the pixel density of the display.
Note that you are supposed to add the smaller image to the
src attribute because this will be the default. For the
srcset attribute, you also need to use the
2x descriptor so that the browser will know that this is the image intended for high-resolution screens.
You can add more than one value to the
srcset attribute, too. For instance, with the following code, you can serve images for 4K monitors:
<img src="images/myimage.jpg" srcset="images/myimage-2x.jpg 2x, images/myimage-4x.jpg 4x" alt="my image">
To add more than one image source to
srcset, you need to use the respective
x descriptors (
4x, etc.) and separate the value pairs by commas.
srcset attribute with
w descriptor(s) and the
If you want to target both the pixel density and the layout size of the user’s device, you’ll need to use the
srcset attribute together with one or more
width descriptors and the
w descriptor defines the width of a source image. For instance,
600w indicates that the image is 600px wide. As long as you separate them with commas, you can add as many image sources to the
srcset attribute as you want, like so:
<img src="images/myimage-small.jpg" srcset="images/myimage-small.jpg 300w, images/myimage-medium.jpg 600w, images/myimage.jpg 1200w, images/myimage.jpg 1800w" sizes="(max-width: 500px) 100vw, (max-width: 1000px) 90vw, calc(60vw - 20px)" alt="my image">
If you use
width descriptors, you’ll need to use the
sizes attribute, too — otherwise, the browser won’t be able to find out which image source to choose. The
sizes attribute is frequently misunderstood because people tend to assume that the width values used in the media conditions of the
sizes attribute are related to the
w values (
600w, etc.) used in the
srcset attribute — however, they are independent of each other.
sizes attribute serves two purposes:
- informs the browser about the space it needs to allocate for the image, depending on the layout
- allows the browser to pick the best-fitting image using the width of the available images (defined by the
wdescriptors) and the pixel density of the screen:
- Example: for a smaller, full-HD screen — say, 13.3” — the browser can pick the same image, such as the
1200wone, as it does for a larger, low-res screen — let’s say 17.3”. The browser’s choice also depends on the size distribution of the available images and other conditions because, as I’ve mentioned above, the
sizesattributes are optional but not obligatory for the browser (as opposed to the
- Example: for a smaller, full-HD screen — say, 13.3” — the browser can pick the same image, such as the
In the above code example, I’ve defined three layouts within the
sizes attribute. On viewports smaller than 500px, the image will span across 100 percent of the viewport width (
100vw). On medium viewports that are smaller than 1000px, the image will need 90 percent of the viewport width (
90vw). And, on viewports larger than 1000px, the browser will need to allocate 60 percent of the viewport width, minus 20px for the left and right margins (
calc(60vw - 20px)). The latter is also the default value, so I haven’t used any media conditions here.
Using media conditions
You can use any number of media conditions to define different layouts and the space the image will need in each, but it is important to note that adding media conditions to the
sizes attribute is optional. It only has to include one default value, and if your layout looks the same at all viewport sizes, you don’t need to use any media conditions. For instance, the above example could alternatively look like this:
<img src="images/myimage-small.jpg" srcset="images/myimage-small.jpg 300w, images/myimage-medium.jpg 600w, images/myimage.jpg 1200w, images/myimage.jpg 1800w" sizes="60vw" alt="my image">
Beware: you’ll find some tutorials saying that you can use the
w descriptors together, but according to the docs, these two shouldn’t be used together. This does make sense when you think about it, as when you use
w descriptors, the browser also considers pixel density, too.
So, if you just want to target pixel density, use
x descriptors. If you want to target both pixel density and layout, use
<img> element used with the
srcset attribute and
w descriptors is an excellent solution if you want to serve the same image in different sizes, sometimes you’ll want to load visually different images for different user agents.
There are two main use cases that cover why you might want to do that:
- using different art directions for different media conditions, for example, zoomed-in and zoomed-out versions of the same image
- using different image formats so that you can serve next-generation images for modern browsers
Now, let’s see what the syntax looks like.
Image sources with different art directions
Let’s say you have the following image:
This is a perfect image for large screens, but for medium-sized screens you might want to serve a closer shot that’s also smaller in size:
And on small screens, you just want to show one of the flamingos in a much smaller image:
You can define these three image sources using the following HTML code:
<picture> <source srcset="images/flamingos-closer.jpg" media="(min-width: 768px) and (max-width: 1199px)"> <source srcset="images/flamingos-far.jpg" media="(min-width: 1200px)"> <img src="images/flamingo-profile-small.jpg" alt="flamingo"> </picture>
As you can see above, we added the smallest, closest image as the default and defined a relevant media condition for each image source.
If you want, you can also specify more than one image within each
srcset attribute using the aforementioned
w descriptors, for example:
<picture> <source srcset="images/flamingos-closer.jpg, images/flamingos-closer-2x.jpg 2x" media="(min-width: 768px) and (max-width: 1199px)"> <source srcset="images/flamingos-far.jpg, images/flamingos-far-2x.jpg 2x" media="(min-width: 1200px)"> <img src="images/flamingo-profile-small.jpg" srcset="images/flamingo-profile-small-2x.jpg 2x" alt="flamingo"> </picture>
Note that with the
media attribute, you can target both width and any other media features, such as orientation, aspect ratio, and more.
If you use the
<picture> element with the
<source> tag, the user’s browser will still load only one image — the most suitable one — but now, the media condition is not a hint or an option like before. Instead, it’s a rule that the browser has to follow in any case. In other words, the browser will assume that the image sources are visually different from each other and treat them accordingly.
Image sources in different formats
Besides media conditions, you can also serve an image in different formats. This can be especially useful if you want to use next-generation image formats, such as AVIF or WebP, that are smaller in size (which can improve performance on their own) but are not supported by older browsers.
<picture> <source srcset="images/myimage.avif" type="image/avif"> <source srcset="images/myimage.webp" type="image/webp"> <img src="images/myimage.jpg" alt="my image"> </picture>
If you use the code above, the user’s browser will check the consecutive MIME types one by one — so, add the one that you want to have checked first (I used AVIF in the example above).
Technically, you could also use the
media attributes together within each
<source> element — however, note that this can add a lot of extra complexity:
<picture> <source srcset="images/flamingos-closer.webp" media="(min-width: 768px) and (max-width: 1199px)" type="image/webp"> <source srcset="images/flamingos-closer.jpg" media="(min-width: 768px) and (max-width: 1199px)" type="image/jpeg"> <source srcset="images/flamingos-far.webp" media="(min-width: 1200px)" type="image/webp"> <source srcset="images/flamingos-far.jpg" media="(min-width: 1200px)" type="image/jpeg"> <source srcset="images/flamingo-profile-small.webp" type="image/webp"> <img src="images/flamingo-profile-small.jpg" alt="flamingo"> </picture>
Browser support for the HTML syntax related to responsive images is relatively good:
sizesattributes are supported by Edge 16+, Firefox 38+, Chrome 38+, and Safari 9+, but they’re not supported by any version of Internet Explorer
<picture>element is supported by Edge 13+, Firefox 38+, Chrome 38+, and Safari 9.1+, but it’s not supported by any version of Internet Explorer
<source>element is supported by Internet Explorer 9+, Edge 12+, Firefox 15+, Chrome 90+, and Safari 14.1+
As Chrome and Safari began to support the
<source> element relatively late, it comes with the lowest level of browser support out of these four HTML features related to responsive images.
Plus, the responsive image syntax degrades gracefully. If a browser doesn’t support one or more elements or attributes, it will simply use the default image added to the very well-supported
Adding different image sources in HTML to serve the most performant image for each user agent can result in a huge performance gain, especially on mobile devices. However, a complex syntax can make your coding workflow overly complicated and your code harder to read.
Essentially, the trade-off is between complexity and performance, so you need to decide whether it’s worth the hassle for you or not. Obviously, image performance is more important on image-heavy websites, and may not be as important if you have just one or two images on the screen at any given time.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Compare NestJS vs. Express.js
You can also automate the process. CDNs such as Netlify and Cloudflare and CMSs such as WordPress already come with built-in image optimization capabilities — they generate several versions of the same image, add the
<picture> element, apply media conditions, and more. There are also open-source solutions that can help you with automation, such as the Image Responsiver or Get Sizes tools.
Finally, don’t forget that this guide only discusses how to serve responsive images in HTML. You’ll still need to use CSS to adapt the image added by HTML to the design, such as adjusting the
max-width attributes or using a responsive image grid.
LogRocket: Full visibility into your web and mobile apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.