<picture> is an HTML5 element designed to give us more versatile and performant responsive image functionality. Instead of loading a single image and trying to resize it to suit all possible viewport sizes and layouts, the picture tag loads multiple images of different sizes and resolutions, choosing the best fit for different scenarios.



How Does <picture> Work?

It works similarly to the way <audio> and <video> elements work, allowing you to place multiple source tags within the parent <picture> element, each using the srcset and sizes attributes to specify different image files along with the conditions under which they should be loaded.

However, as powerful as the <picture> element is, sometimes it gives us more power than is actually needed to achieve suitable responsiveness. Sometimes all it really takes is a regular <img> element using the srcset and sizes attributes inline.

Coming up in This Tutorial

In this tutorial we’ll solve any confusion you might have. We’ll see what the srcset and sizes attributes can do, how to use them with an <img> or <picture> element, and how to know which combination is the right choice.

Use these links to skip to each section:

1. Start with a Default <img> Element

This is our starting point, a regular old vanilla <img> element with an alt attribute to provide a text based description.



At the base level of responsive images this is typically paired with a little bit of CSS that allows this single image to shrink if its parent container becomes too small to hold it:



This does the essential job of making sure the image isn’t sized dramatically incorrectly, but it still leaves us with just one image for all cases, regardless of how well (or poorly) that one image works:



One fluid image for all circumstances

It solves the problem in one respect, allowing us to display the same image under many different circumstances. But it doesn’t allow us to specify different images for differing circumstances. For example, it’s quite possible that the original image is impractically large (in terms of file size) for some users to download on their mobile network.

Tip: Take a look at What Does My Site Cost? by Tim Kadlec to get an idea of the real cost of mobile data for users all over the world.



“According to the July 1, 2019 run of HTTP Archive, the median site now weighs 1937kb.” – What Does My Site Cost?

To start working up a better solution let’s move on to srcset .

2. How to Use “srcset” for a Set of Images



Instead of using just one image everywhere, it’s much better if we can have a set of images we load depending on the size of the viewport, loading large images for wide viewports and small images for narrow viewports.

That’s what the srcset attribute is for: a set of images rather than just one via the src attribute.



Width Switching



When an image is loaded with only a regular src attribute the browser doesn’t know how wide it is until after it’s loaded. But with the srcset attribute we can tell the browser how wide each of our images is in advance. It can then use that information to load the most appropriate image depending on the size of the viewport at the time.

You’ll still use the src attribute when using srcset as this provides the default image the browser should use, and acts as a fallback if someone is using an old browser that doesn’t support srcset .

Go Mobile First

It’s a good idea to follow a mobile first approach here and load your smallest image via the src attribute. Then add your default image and its larger alternative images inside the srcset attribute as a comma separated list, specifying the width of each, (after a space), with [width]w :

It’s important to include your default image in the srcset along with its width, despite advice you may have heard to the contrary, or the browser will not include it in the list of options to choose from and so it will never load at any viewport width.

With the above code the browser will load the small image at small viewport sizes, the medium image at medium viewport sizes, and the large at large viewport sizes. (Describing viewport sizes very roughly).



If the browser doesn’t support srcset , (something that’s only an issue for quite old browsers), it will fall back to showing the small image. If catering for older browsers is necessary for your projects be sure to include some CSS to scale your default image to the correct size.

Note: Despite the fact the above code will work as is, according to the srcset attribute spec if you use srcset for width switching, you must also include a sizes attribute, which we will talk about shortly.

Pixel Density Switching



You can also use srcset to load images according to their DPI instead, but instead of specifying their width you should show their pixel density represented as [density]x :

However, you cannot use pixel density and width in the same srcset attribute, and you cannot use pixel density specifications with the sizes attribute we are about to add into the mix. For that reason, you are generally more likely to find you’ll want to use width specifications in your srcset attributes.

3. Using “sizes” to Control Image Layout

The sizes attribute allows you to specify a width for the image layout. Note this is a separate concept to the real image widths specified per file in the srcset attribute. The width given in sizes is related solely to layout, and can be thought of as creating empty placeholder slots into which the browser can insert images from your srcset .



It doesn’t matter which image file the browser chooses to load from your srcset , it will display it at the width you specified in sizes.



For example, if you wanted your image to always appear at 80% the width of the viewport you could use:

Note: percentage values are not allowed, but vw (viewport width) values are.

In this example the browser will still choose between the small, medium and large images depending on the size of the viewport, but whichever image is chosen it will display at a width of 80vw .

Adding Media Conditions



In our example above we used only one value in the sizes attribute, but you can conditionally change up your image layout by adding “media conditions”. Media conditions are the true or false states we evaluate when we use media queries, and when evaluated in sizes allow you lay out your images differently depending on things like viewport width.

For example, we might want an image to be laid out at a width of 80vw so there is some empty space to its left and right, as per our previous example. However we might only want to spare that much of our viewport if it’s sufficiently wide, say a minimum of 60rem .



We can achieve this by changing our code to add the media condition (min-width: 60rem) before our 80vw size, like so:



Now the image will only be sized at 80vw if the viewport is at least 60rem wide.



We can also have our image layout default to a width of 100vw if the viewport does not meet the media condition we just added. Because a default value in sizes doesn’t require a media condition, all we need to do for this is add the 100vw value, after a comma:

If we choose to, we can also add another size and media condition in between, so that as the viewport narrows to 40rem we set the length to 90vw :



“sizes” Does Not Directly Determine “srcset” Image Selection



Note that in our examples above, regardless of the layout we give our images via the sizes attribute, the browser will still select the image of best fit automatically, just as it did before we added any media conditions.



You might see it said that you should use media conditions in sizes to determine which image should be loaded from the list in your srcset . This is indirectly the case, but it can be unhelpful to think of the functionality in this way.



The reason I say that is you might be led to believe you need to use media conditions within sizes the way you might use them in regular media queries, i.e. designating a specific media condition to trigger each image you want to load. However this is not quite how things work here.

Rather, the browser is always going to handle the image selection automatically.



What’s really happening is that with sizes you’re saying:



“At [this] viewport size I want an image slot [that] wide.”



You’re not saying which specific image file you want to load, just that you want to create a placeholder “slot” of a certain size to fit your layout.



And with srcset you are saying:



“Here are my images, pick whichever one you think is best.”



The browser will generate empty image “slots” based on your sizes attribute, then choose the best fitting images from your srcset to fill them.

At a glance your slot sizes and image widths won’t necessarily seem to closely correspond. You might have four images in your srcset and only two flexibly sized “slots” in your sizes and still all four images could be used at different times by the browser.

We saw something like this in our earlier example, where we created a “slot” size that is active between 40rem and 60rem viewport width, and lays out an image with a width of 90vw .

This pixel width of this slot could hence calculate to anywhere between roughly 570px and 860px . In our example we provided images at widths of 320px , 800px and 1200px .



The browser will generally choose the smallest image from the srcset that is still wider than the current “slot”. As such, if the “slot” were at a width of of 810px it would load the 1200px image, but if the slot were at a width of 790px it would load the 800px image. One media condition, two possible images.

The only reason what you put in sizes influences which image is selected from srcset is that it changes the size of “slot” the browser is trying to best fit. But the two concepts are still quite separate and should be thought of as such.

So to summarize:



First, think of your image layout as a series of placeholder slots and decide what size those slots should be. Then, create a set of images with widths that will best fit the slots you have decided to create.

4. When to Use the <picture> Tag

So far we’ve talked a lot about srcset and sizes , and you might be wondering, what about the <picture> element?



Use for “Art Direction” Not “Resolution Switching”

With our examples so far we’ve purely been swapping images from a set in which the width or pixel density might differ, but the aspect ratio and orientation remains identical in each case. This is known as “resolution switching”.

But sometimes you want to do more than that. Sometimes you want to include images for the browser to choose from that are cropped differently, or you might want to provide both landscape and portrait orientation options.



Adding these types of images into the mix is known as “art direction”, and that’s where the <picture> element comes into play, adding another layer of responsive image functionality on top of what we’ve looked at so far.

<picture> is entirely capable of handling resolution switching, however you shouldn’t use it if that’s all you need. In such cases stick with a regular <img> element plus srcset and sizes . However, if you need art direction as well, it's <picture> time.

Different images served, depending on the circumstances, thanks to the picture tag

Use for Partially Supported Image Formats

On top of using <picture> for art direction, you can use it when you want to deploy newer image formats like WebP but still fall back to fully supported formats like PNG. We'll see how shortly.



5. How to Use <picture>

The <picture> element does nothing in and of itself. Rather, it is designed to wrap around an <img> element and one or more <source> elements. The <source> elements give the browser more information to help it decide which file it should render, and at what size, through the <img> element.

The <picture> element’s child <img> element is required, and that child should not have its own srcset and sizes attributes because that functionality will be shifted to the <source> element. Further, the <img> element must also have src and alt attributes. So let’s take the example code we’ve put together so far and convert it into the format <picture> requires:

To reintroduce the functionality we had in our srcset and sizes attribute we can add a <source> element with the same attributes, like so:

That has everything working again, but as we mentioned earlier, there’s no point in using <picture> if all we’re doing is resolution switching. So let’s imagine all our currently loaded images are in landscape orientation, then we’ll add in a whole second set of images for use in portrait orientation instead.

The first thing we’ll do is specify that our current set of images should only be used when the viewport is in landscape orientation. In our <source> element we can do this by adding a media attribute with the value (orientation: landscape)

From here, we can add a second <source> element that only activates when the site is in portrait orientation. This element will provide a new srcset , with new image widths, but will still use the same layout sizes as before. This way we’ll keep the same amount of empty space either side of the image at various viewport dimensions:

There we go, now we’ll have portrait images in portrait orientation, and landscape images in landscape orientation–some nice art direction to go along with our resolution switching.

We could build on this a lot further if we needed to. For example, as well just having a set of regular landscape images, we could have <source> element with a set of wide-landscape images. We could set this to show if the site is both in landscape orientation and at least 1200px wide: media="(orientation: landscape) and (min-width: 1200px)"



Note: While the media and sizes attributes both contain media conditions, they are not used in the same way. The sizes attribute is specifically used to create a collection of layout sizes, with a layout width specified after each condition. The media attribute contains just a media condition, and only if it evaluates to true does the <source> element it’s attached to activate.



Additional note: I have seen it said you should not simultaneously use the media and sizes attributes on a <source> element, however I have not found verification of this in the spec, and in my tests the two seem to work together swimmingly.



<source> Order Matters

When constructing your <picture> elements, be mindful of the fact that as soon as the browser hits a <source> element with a media attribute that returns true it will stop looking and render from the srcset for that element. So make sure you put media queries with higher specificity first.

For example, we just mentioned the option of having a <source> element with a media query looking for both landscape orientation and a min-width of 1200px . If you first placed a <source> element that needed only landscape orientation, it would activate before the browser got the chance to go any further. So in this example don’t do this:

Do this:

Using <picture> for Partially Supported File Types

The fact that <picture> will go through a stack of <source> elements until it finds an image it can load successfully means you can use it quite handily to load newer file formats that don’t have 100% browser support yet.

In this example, if WebP is supported in the browser it will load as it’s specified in the first <source> element. If the browser cannot load the WebP image it will attempt to load the SVG from the second <source> element. And finally if neither are supported, the PNG will load instead:

You’ll notice that in this example the type attribute is used. When loading alternate file formats through <picture> you should specify the MIME type in this way to allow the browser to instantly check on file type support as soon as it hits the <source> element in question.

A Note on Accessibility

Screen readers will use the alt text supplied in the fallback <img> element for whichever image is displayed in the browser. Make sure, therefore, that the alt text represents all the images equally well!

6. Browser Support for <picture>

Browser support for the <picture> element is very solid nowadays, though like many other aspects of modern CSS and HTML Microsoft lean on the Edge browser, rather than IE, to fly the team colors.

By using the fallback <img> tag within <picture> you’re still catering for anyone using a non-supporting browser.

Conclusion

Between srcset , sizes and <picture> we have incredibly robust and extensive control over your responsive images, as well as the ability bringing new file formats onto the stage with graceful degradation.



Useful Resources