Responsive Images

A Reference Guide from A to Z

Chapter 1 - What is responsive images?

In this guide, we will learn everything related to responsive images along with sample code. Basic knowledge of HTML and CSS is required to understand the concepts discussed here. After you finish reading, you will know the latest tools & techniques to implement responsive images correctly.

Responsive images are the set of techniques used to load the right image based on device resolution, orientation, screen size, network connection, and page layout. The browser should not stretch the image to fit the page layout, and loading it shouldn’t result in time & bandwidth wastage. It improves user experience as images load fast and look crisp to the human eye.

Quick example of the responsive images in HTML

The best way to understand responsive images is with a quick example. For simplicity, we will load a 2200px wide image on different devices. Everything else remains the same except the viewport size.

The markup would be:

<!-- The width of the orignal image is 2200px -->
<img src="image.jpg" />

For desktop (iMac), the image is optimal because the width of the viewport and image matches.

However, in mobile, as we can see, the viewport is only 375 CSS pixels wide. The same is valid for the tablet. The viewport is only 1024 CSS pixels wide. We are wasting time and bandwidth downloading this overly large file.

What’s happening?

This is a simplified version of responsive images in action. 🙌

We used srcset (source set) to provide the browser with three different size images. The browser picked the right option based on the actual viewport size of the device. We will soon discover more about srcset and other options in great detail.

Note that we are still using the old src attribute as a fallback if the browser doesn’t support the srcset attribute. As of Oct 2020, all browsers support srcset except Opera mini and IE.

Chapter 2 - Why do we need responsive images?

As you saw in the above example, one size doesn’t fit all. A responsive design should adapt based on user screen size, pixel density, and device orientation to ensure a great user experience.

Here are three main reasons why we need to implement responsive images:

  • Render a high-quality image on different devices
  • Loading the right image - Art direction
  • Faster loading web pages

1. Render a high-quality image on different devices

An image without perceptible artifacts looks crisp and contributes towards a great user experience. This is especially important on retail sites where users expect to view high-resolution closeups of product images to better look at texture & details.

For an image to look good, it must adapt based on viewport width and screen pixel density. Let’s understand these:

Different viewport width case

If your layout changes based on the device viewport, the loaded image dimension should match the container CSS/HTML width. If the browser stretches a smaller image to fit the design, the rendered image will look blurred, and if you load a bigger image on a device with small viewport width, it results in bandwidth and time wastage.

Different pixel density case

High-resolution displays have a higher pixel density. This means more pixels in the same amount of physical space. As a result, high-resolution displays demand images with more pixels. In simple terms, a large image is required to fit the same physical image.

Reference

Consider an iPhone8:

  • CSS viewport size: 375px by 667px
  • Pixel density: 2
  • Effective device resolution: 750px by 1334px

It means if we want to render a 100 CSS pixels wide image, we will have to load a 200px wide image for it to look sharp.

For a device with pixel density 3, we will have to load a 300px wide image.

However, note that it does not always mean that we have to load a large file size. On a device, with DPR (device pixel ratio) 2, a 2x wide image with low-quality (50) will look better than a 1x wide image with high-quality(90). Despite being similar in terms of file-size, the 2x image will give a sharper appearance. You can learn more about this technique.

2. Loading the right image - Art direction

When a large image is resized to fit a small area, it can lose its relevance, usefulness, and legibility. The most important part might get cropped.

For example, consider this screenshot of a dashboard on a product landing page. It is highlighting different elements of the user interface to a potential user.

On the desktop, the above image is perfect. However, if we downscale the same image to fit a mobile viewport, we are losing many essential elements of the page.

In this case, an altogether different image might have been more appropriate, for example:

It is called art direction. By "art directing", you can explicitly decide which image should be shown based on the image's size on the page. We will cover this technique in more detail soon.

You can also use this to show different images in a mobile device based on orientation - landscape vs portrait mode.

3. Faster loading web pages

According to the HTTP archive data, 64% of a website’s page weight, on an average, is made up of images. With mobile traffic surpassing desktop traffic, it has become even more crucial to optimize images for different device sizes.

Ideally, you would like to serve images that are resized to match the user’s viewport dimensions. Without a means to do this, you will have to send an overly large image to all users. This means that the user on a mobile device with a small viewport width has to download that extra data, which slows down the page load. This is a wastage of time, bandwidth, and money and degrades the overall user experience.

Chapter 3 - How to make images responsive?

There are many methods for implementing responsive images. We have come a long way from using hacky javascript based solutions to having native support in modern browsers.

In this chapter, we will cover the latest methods for implementing responsive images.

Let’s have a quick comparison before discussing each of these techniques in more details:

  • Srcset
  • Srcset with sizes
  • Picture element
  • Client-hints
  • Responsive images in CSS
MethodWhen to useMarkup complexity
Using srcsetFor fixed-size images that take roughly whole viewport width, e.g., full-width promotional banner.Simple
Using srcset + sizesFor flexible images. When the layout & image size changes based on the viewport width. For e.g., a three-column layout on desktop vs a two-column layout on the mobile device.Slightly complex
Using picture elementWhen you want to load an entirely different image based on screen size i.e. art direction. Or you want to use the latest image formats like webp or avif on a supported device.Highly complex.
Using client-hintsWhen you don’t want to make major changes in the HTML markup.No major code change
Responsive images in CSSWhen you are loading images via CSS styles as a background image.Simple

Chapter 4 - Srcset

The standard <img> tag allows us to define a single image source. As a web developer, it becomes our responsibility to make sure that the right source is defined. But since we don’t have all the information about the device beforehand, it becomes tricky to create a bulletproof markup that loads optimal images for all resolutions.

Remember the little conversation between a web developer and a browser?

srcset allows you to define a list of different image resources along with size information so that browser can pick the most appropriate image based on the actual device’s resolution.

Each comma-separated item in srcset has:

  1. Image URL, e.g. http://ik.imgkit.net/demo/default-image.jpg or relative path /demo/default-image.jpg
  2. An empty space
  3. The actual width of the image or display density:
    • Either using display density descriptor, for example, 1.5x, 2x etc.
    • Or, using width descriptors, for example, 450w. This is the width of the image in pixels.

Using display density descriptor

The syntax for display density descriptors is straightforward. srcset provides a comma-separated list of image resources along with display density it should be used, for example1x, 2x etc.

<img src="image.jpg" 
     srcset="image.jpg,
             image_2x.jpg 2x"
/>

If a display density descriptor isn’t provided, it is assumed to be 1x.

Demo - srcset with density descriptor

Let’s see this in action with a live demo - https://imagekitio.github.io/responsive-images-guide/srcset-density.html.

Using device emulator in Chrome, at DPR 1, the image currentSrc is https://ik.imgkit.net/ikmedia/building.jpeg?tr=w-300

Similarly, if we consider different DPR values:

DPR valueImage picked by browser (currentSrc)
1?tr=w-300
2https://ik.imgkit.net/ikmedia/building.jpeg?tr=w-600
3https://ik.imgkit.net/ikmedia/building.jpeg?tr=w-900

When to use display density descriptors

Use display density descriptors if your images are of fixed width, and the only thing that varies is display density. It is never that simple though 😉

Challenges in using display density descriptor

As per pixensity.com, there are more than 300 different types of phones, tablets, laptops, and desktop devices with varying screen sizes and display density.

The critical question becomes, do you want to track different display densities available, for example, 1x, 1.5x, 2x, 2.5x, 3x 4x, etc. More often, you will need to go beyond taking care of display density, so this method won’t be sufficient.

Using width descriptor

The syntax is similar to the display density descriptor, but instead of display density values, we provide the actual width of the image.

<img src="image.jpg" 
     srcset="small.jpg 300w,
             medium.jpg 600w,
             large.jpg 900w"
/>

This lets the browser pick the best image

Using width descriptor allows the browser to pick the best candidate from srcset based on the actual width needed to render that image on that particular display at runtime.

Note that display pixel density is also taken into account by the browser while calculating the required width. 😎

For example, assuming an image takes up the whole viewport width - On a 300px wide screen with DPR 2, the browser will pick medium.jpg because it needs a 300x2=600px wide image. On a 300px wide screen with DPR value 3, the browser will select large.jpg because it needs a 300x3=900px wide image.

Demo - srcset with width descriptor

Let see this in action with a live demo - https://imagekitio.github.io/responsive-images-guide/srcset-width.html.

Using the device emulator, let’s resize the screen to 250px and set DPR at 1. The image loaded in this case is https://ik.imgkit.net/ikmedia/building.jpeg?tr=w-300

Similarly, if we consider combinations with different viewport width and DPR values:

Viewport widthDPR valueFinal width of the image required (width × DPR)Image picked by the browser (currentSrc)
250px1250 × 1 = 250pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-300
250px2250 × 2 = 500pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-600
250px3250 × 3 = 750pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-900
300px1300 × 1 = 300pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-300
300px2300 × 2 = 600pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-600
300px3300 × 3 = 900pxhttps://ik.imgkit.net/ikmedia/building.jpeg?tr=w-900

Notice how the browser is taking display density and viewport width into account while calculating the required image's final width.

Challenges with using only srcset

If you are loading a fixed-width image that will take up almost full viewport width, then use srcset along with width descriptors.

However, in the below cases using srcset alone won’t be sufficient:

  • When you are loading flexible images i.e. when the size of your image changes based on the size of the viewport. For example, a single-column layout on the mobile and a three-column layout on desktop devices will need different size images.
  • Or when the image is only taking a fraction of viewport width. Since the browser is not aware of the layout, it will assume the image will take up the whole viewport width and download a much bigger image than required.

Wait. What? The browser is not aware of the layout!

It is logical to think that browser has stylesheets to find our required size of the image element but note that those stylesheets are not parsed yet. If the browser waits till all stylesheets are parsed & executed, it will inevitably delay the downloading of images.

To understand this better, let’s first look at how the browser loads a page.

How browser loads a webpage — the old way

A web page is made up of text, CSS, JS, and fonts. A lot happens in the background once you hit enter, and a page is finally rendered on your screen.

  • First, the HTML is downloaded, and the browser starts parsing it. If it comes across an external style sheet, it starts downloading it in parallel and continue parsing HTML.
  • If it comes across an inline <script> tags, it pauses the HTML parsing and executes the script right away.
  • If it comes across a <script> tag (one without defer or async attribute) that points to an external URL, it pauses the HTML parsing and first download and executes that Javascript resource.
  • If it comes across an <img> tag, it starts downloading the image resource in parallel and continues parsing HTML.
  • Once all blocking external style sheets and Javascript is downloaded, parsed, and executed, the page is rendered.

For example, if we have a HTML like this:

<script src="vendor.js"></script>
<script src="app.js"></script>
<script src="zoom.js"></script>
<img src="image1.png">
<img src="image2.jpg">

– the browser loads resources like this:

Pausing the parser whenever a script is encountered results in sub-optimal use of the browser’s ability to download multiple external resources over the network in parallel. This method is delaying the download of resources required to render the page.

Pre-loaders or speculative parsing to rescue - the new way

Internet Explorer, WebKit, and Mozilla all implemented pre-loaders in 2008 to improve the low network utilization problem that we just discussed.

Essentially, the idea is that the browser cannot build DOM while executing a script but can still parse the rest of the markup looking for other resources, for example, stylesheets, javascript files, or images that are linked. These files are added to a list and start downloading in the background. By the time all scripts execute and HTML parsing finishes, hopefully, the browser has already downloaded these resources, and there won’t be any further delay.

The waterfall chart for the example above now looks like this:

Now we understand why srcset alone is not sufficient for the browser to understand the image's required size. To overcome this problem, we have the sizes attribute.

Chapter 5 - Srcset with sizes

sizes attribute contains a comma-separated list. Each item in the list describes the size of the image in relation to the viewport.

Using the sizes attribute with srcset provides the browser with enough information to start downloading the right image as soon as it sees an <img> tag in HTML without waiting for styles sheets to complete downloading and parsing.

Why do we need sizes? If you scrolled here directly and wondering why the browser is not aware of how big the image will render, checkout how the browser loads a web page.

The syntax:

<img src="image.jpg" 
     srcset="small.jpg 300w,
             medium.jpg 600w,
             large.jpg 900w"
     sizes="(max-width: 300px) 100vw, (max-width: 600px) 50vw, (max-width: 900px) 33vw, 900px"
/>

Each comma-separated item in sizes has:

  1. Media conditions, for example, (max-width: 300px) - It describes a possible state that the screen can be in. (max-width: 300px) means when the viewport width is 300 CSS pixels or less. It is similar to media queries but with some limitations. You cannot use screen or print.
  2. An empty space.
  3. The width of the image element when the media condition is true. You can provide an absolute length (px, em) or a length relative to the viewport (vw), but not percentages.

Demo - srcset with sizes

Let’s see this in action with a live demo - https://imagekitio.github.io/responsive-images-guide/srcset-sizes.html

The layout is such that:

  • If viewport width is above 900px, each image takes a fix 225px width.
  • Upto 900px, each image takes up 33vw i.e. 33% width of total viewport width.
  • Upto 700px, each image takes up 50vw i.e. 50% width of total viewport width.
  • Upto 400px, each image takes up 100vw i.e. the whole viewport width.

HTML markup of a single image element looks like this:

<img src="https://ik.imgkit.net/ikmedia/women-dress-1.jpg" 
     srcset="https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-225 225w,
             https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-300 300w,
             https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350 350w,
             https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-640 640w"
     sizes="(max-width: 400px) 100vw, (max-width: 700px) 50vw, (max-width: 900px) 33vw, 225px">

Let’s see what happens at different screen size and DPR values -

Viewport widthDPR valueImage size required (width × DPR)Image picked by the browser (currentSrc)
350px1100vw i.e. 350 × 1 = 350pxhttps://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350
350px2100vw i.e. 350 × 2 = 700pxClosest candidate is https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-640
650px150vw i.e. (650/2) × 1 = 325pxClosest candidate is
https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350
1024px1225 × 1 = 225pxhttps://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-225
1024px2225 × 2 = 450pxClosest candidate is
https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350

How to choose breakpoints for srcset and define sizes?

When implementing responsive images, you will have to write the values for the srcset and sizes attributes. It can quickly get tricky. So let’s repeat the purpose of these two attributes so that it becomes easy to derive the values:

  • srcset - To define multiple image sources of different widths and let the browser pick the most appropriate candidate during HTML parsing.
  • sizes - To define the size of the image element. It could be a fixed size like 225px or relative to the viewport. You can use CSS media conditions here to provide different size values based on the viewport width.

Before you provide different image sources in srcset, you need to understand what all sizes do you need based on the layout. It is going to be site-specific, meaning it is closely tied to your CSS.

Here is a simple approach to this problem.

While calculating sizes, think in terms of image width relative to the viewport. For example - “My layout is such that my image is going to be roughly X percent of the viewport if the screen size is above Y px.”

Let’s understand this with a few examples.

sizes="(min-width 1024px) 33vw, 95vw)"

It means - “The image is in a three-column layout on a screen larger than 1024px. Otherwise, it is close to full viewport width leaving some space around it”.

Now, let’s pick the sizes from our demo - https://imagekitio.github.io/responsive-images-guide/srcset-sizes.html

sizes="(max-width: 400px) 100vw, (max-width: 700px) 50vw, (max-width: 900px) 33vw, 225px"

It means -

  • If viewport width is upto 400px, each image takes up 100vw i.e. the whole viewport width.
  • Upto 700px, each image takes up 50vw i.e. 50% width of total viewport width.
  • Upto 900px, each image takes up 33vw i.e. 33% width of total viewport width.
  • Above 900px, each image takes a fix 225px width.

Defining image width relative to viewport does not always provide the most optimal image considering many devices & corresponding viewport width & DPR values. However, it is a practical solution.

Once we know the value of sizes, it is easy to find out what all different size images we need to define in srcset -

Image size candidates from sizesEffective size at different DPR values
(width × DPR)
400px
From the first media condition i.e.
max-width: 400px) 100vw
400px at 1x
800px at 2x
1200px at 3x
350px
From the second media condition i.e.
(max-width: 700px) 50vw
350px at 1x
700px at 2x
1025px at 3x
300px
From the third media condition i.e.
max-width: 900px) 33vw
300px at 1x
600px at 2x
900px at 3x
225px
From the fourth default media condition i.e.
225px
225px at 1x
450px at 2x
675px at 3x

If you sort the effective sizes, here is what you will get -

225px, 300px, 350px, 400px, 450px, 600px, 675px, 700px, 800px, 900px, 1025px and 1200px.

If you are using an image CDN like ImageKit.io, it is easy to provide images in different dimension by addign URL parameters e.g.

However, you don’t have to provide images at all required sizes in srcset. You can select a few candidates, and the browser will pick the closest one. This serves two purposes:

  • You don’t have to generate and store multiple variants of the same image.
  • If you are using a CDN for faster delivery and caching, then having a few variants will improve your cache hit ratio as the repeat request will increase.

So you can pick three or four candidates and write srcset like this:

srcset="https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-225 225w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350 350w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-700 700w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-900 900w"

Tips for choosing breakpoints while writing srcset

  • You can look at your Google analytics device report to see what screen resolutions you should care about most. Accordingly, you will know which layout needs the most optimization. For example, assuming that 60% of the users on your website see a three-column layout and 20% see a single column layout. Then you can consider all size variations you need for these two layouts at multiple DPR values and write srcset.
  • To increase cache hit ratio on CDN, don’t try and provide srcset for all possible breakpoints and sizes. CDN cannot cache all your resources. Some resources have to evict to make space for others. This would cause more performance bottlenecks than gains.
  • When in doubt - First think about your layout, i.e. CSS ➡️ , then accordingly write sizes ➡️ And finally choose breakpoints for srcset considering different DPR values and effective image sizes you need 🙌.

Chapter 6 - Using picture element

srcset and sizes are useful to define multiple dimension variants of the same image. But if you need art direction - that is, to explicitly dictate browser to load an entirely different image based on browser viewport or image format support, you need <picture> element.

When to use the Picture element

Picture element should be used to achieve:

  • Art direction
  • Different format support
  • Color theme example

The syntax:

<picture>
  <source srcset="/large.jpg"
          media="(min-width: 800px)">
  <source srcset="/small.jpg"
          media="(min-width: 400px)">
  <img src="/large.jpg" />
</picture>

<picture> element consists of zero or more source and one img element. The browser will consider each source element to choose the best match based on device display and image format support.

Each source accepts media and type attributes in addition to well known srcset and sizes.

Media attribute

media attribute contains a media condition like CSS media query. If a source’s element media condition evaluates to false, the browser skips that source. If none of the source element’s media conditions evaluate to true, the browser loads the image specified in the img tag.

Example - Loading different based on screen size

<picture>
  <source srcset="/large.jpg"
          media="(min-width: 800px)">
  <source srcset="/small.jpg"
          media="(min-width: 400px)">
  <img src="/large.jpg" />
</picture>

The browser will pick one of the source elements based on the media condition.

Srcset attribute

The srcset attribute is the same as we discussed before. It contains a comma-separated list of different image resources.

Each comma-separated item in srcset has:

  1. Image URL, e.g. http://ik.imgkit.net/demo/default-image.jpg or relative path /demo/default-image.jpg
  2. An empty space
  3. The real width of the image:
    • Either using display density descriptor e.g. 1.5x, 2x etc.
    • Or, using width descriptors e.g. 450w. This is the width of the image in pixels.

Example - Loading different based on device pixel ratio

<picture>
  <source srcset="large_1x.jpg 1x, large_2x.jpg 2x, large_3x.jpg 3x"
          media="(min-width: 800px)">
  <source srcset="small_1x.jpg 1x, small_2x.jpg 2x, small_1x.jpg 3x"
          media="(min-width: 400px)">
  <img src="large_3x.jpg" />
</picture>

Type attribute

type attribute specified the MIME type of the resource URL(s) in the source’s srcset. If the browser supports that MIME type, it will load the resource. Otherwise, it will skip that source and move to the next. If none of the source’s type is supported by the browser, the image in img is loaded.

Example - Loading different image format based on browser support

<picture>
  <source srcset="/image.webp"
          type="image/webp">
  <source srcset="/image.avif"
          type="image/avif">
  <img src="/image.jpg" />
</picture>

Live demo of using the picture element

Let’s see this in action with a live demo - https://imagekitio.github.io/responsive-images-guide/picture.html

There are two <picture> elements in this demo:

  • The first example contains a screenshot of the ImageKit media library to showcase different user interface elements. The interface varies based on screen size. So to depict the same, we are loading a different screenshot based on screen size. For screen size greater than or equal to 800px, you will see a desktop version of the user interface i.e. https://ik.imgkit.net/ikmedia/dashboard.png. Otherwise, you will see a mobile-friendly version i.e. https://ik.imgkit.net/ikmedia/dashboard-mobile.png.
  • The second example demonstrates loading a WebP format image if the browser has support for it. Otherwise, a JPG image is loaded.

Loading different image in dark mode vs light mode

Dark mode lets you change the background color of an app window to black. As a web developer, you can choose to load a different image if a user has turned on dark mode.

<picture>
  <source srcset="dark.jpg" media="(prefers-color-scheme: dark)">
  <img src="light.jpg">
</picture>

The browser will evaluate the media condition (prefers-color-scheme: dark), and if it is true, it will pick that source and load dark.jpg.

Here is how it looks, toggle the dark mode setting and refresh the page to see how the browser loads a different image.

Live demo - https://imagekitio.github.io/responsive-images-guide/dark-vs-light-mode.html

Chapter 7 - Using client hints

What are client hints?

As the name suggests, client hints are the hints provided by the client device to the server along with the request itself. These hints allow the server to fulfill a particular request with the most optimal resource. The latter is known as content negotiation.

Client hints provide this information via HTTP request headers. For example -

How to enable client hints?

Not every request has these HTTP headers. You will have to explicitly tell the browser to include these client hints using a meta tag.

<meta http-equiv="Accept-CH" content="Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width">

This allows the browser to send the Width (the width, in device pixels, of the image container within the responsive layout), DPR value (device pixel ratio) and Viewport-Width (device screen width in CSS pixels) along with image requests.

That’s great. But why do we need client hints in implementing responsive images when we already have srcset and picture?

Why do we need client hints?

To understand the need for client hints, let's take a closer look at what we have learned so far -

  • srcset and sizes to define URLs of different image variants and specify rendered image size so that browser can download the most appropriate image based on the size of the viewport on its own.
  • The picture element to dedicate how the browser should load a differently cropped or entirely different image, which is better suited for a smaller display.

While srcset and picture provides us with everything we need to implement responsive images use-cases, but it could be time-consuming to develop and maintain for complex use cases.

All of the above methods require you to modify markup so that as a web developer, you can pass the essential missing piece of information that the browser needs at runtime to be able to download the right image. Well, client-hints do the same but without the complex markup — they provide the missing link between the browser and the server when it comes to layout information and device capabilities.

Let’s understand with an example

Suppose you have a simple resolution switching use cases where you want to load a different size variant of the same image based on the viewport width. The syntax would be -

<img src="image.jpg" 
     srcset="small.jpg 300w,
             medium.jpg 600w,
             large.jpg 900w"
     sizes="(max-width: 300px) 100vw, (max-width: 600px) 50vw, (max-width: 900px) 33vw, 225px"
/>

However, if you want to load a WebP image in a supported browser, your syntax becomes -

<picture>
  <!-- webp format for (max-width: 300px) 100vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.webp 300w,
                  medium.webp 600w,
                  large.webp 900w"
            type="image/webp">
  <!-- jpg format for (max-width: 300px) 100vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.jpg 300w,
                  medium.jpg 600w,
                  large.jpg 900w">
  <source srcset="/image.avif"
            type="image/avif">

  <!-- webp format for (max-width: 600px) 50vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.webp 300w,
                  medium.webp 600w,
                  large.webp 900w"
            type="image/webp">
  <!-- jpg format for (max-width: 600px) 50vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.jpg 300w,
                  medium.jpg 600w,
                  large.jpg 900w">
  <source srcset="/image.avif"
            type="image/avif">

  <!-- webp format for (max-width: 900px) 33vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.webp 300w,
                  medium.webp 600w,
                  large.webp 900w"
            type="image/webp">
  <!-- jpg format for (max-width: 900px) 33vw -->
  <source media="(max-width: 300px) 100vw"
          srcset="small.jpg 300w,
                  medium.jpg 600w,
                  large.jpg 900w">
  
  <img src="large.jpg" />
</picture>

Yes! It quickly gets complex.

If we use client hints, the above syntax can be reduced to

<meta http-equiv="Accept-CH" content="Sec-CH-DPR, Sec-CH-Width">
...
<img src="/image.jpg" sizes="(max-width: 300px) 100vw, (max-width: 600px) 50vw, (max-width: 900px) 33vw, 225px" />

Pretty amazing, right! No srcset. But for this to work, the server should be capable of understanding client hints and respond appropriately.

We will discuss in detail how this is possible, but the bottom line is — when possible, you should aim to centralize image resizing & processing and automate as much as possible. This is precisely what client-hints do. ImageKit supports client hints out of the box.

Client hints for responsive images

Among others, one of the primary uses of client hints is to send information about the required size of the image in the current page layout. This simplifies the markup and automates a lot of information passing from browser to web server when implementing responsive images.

Let’s put that in perspective by taking a closer look at what all information does the browser need at runtime to be able to load an appropriate image -

  • Viewport width.
  • How big will the image render? It depends upon your layout, which might adapt based on the width of the viewport.
  • Device pixel ratio i.e. 1x, 2x, 3x or 4x.
  • URL of the image resource for different sizes.

You can opt-in the following client hints, which will be sent as request headers along with HTTP request -

  • Sec-CH-Width - Final size of the required image in page layout considering device pixel ratio
  • Sec-CH-DPR - Device pixel ratio
  • Sec-CH-Viewport-Width - Viewport width in CSS pixels.
  • Width - Final size of the required image in page layout considering device pixel ratio (deprecated in favour of Sec-CH-Width)
  • DPR - Device pixel ratio (deprecated in favour of Sec-CH-DPR)
  • Viewport-Width - Viewport width in CSS pixels (deprecated in favour of Sec-CH-Viewport-Width)
  • Accept - This header is always sent with every request by default.
  • Save-Data - on or off to indicate the user’s preference to receive less data.
  • ECT - Effective Connection Type e.g. **4g, 3g, 2g, and slow-2g.
  • RTT - Round Trip Time, in milliseconds, on the application layer.
  • Downlink - Approximate downstream speed of the user’s connection in megabits per second (Mbps).

Sec-CH-Width, Sec-CH-DPR, and Sec-CH-Viewport-Width hints are most relevant to responsive images implementation as they allow the webserver to control the image size from the backend.

Accept hint can be used by the server to deliver images in next-generation format e.g. WebP or AVIF, without changing the image source URL or using the <picture> element.

The server can use network hints such as Save-Data, ECT, RTT, and Downlink to deliver a low-quality variant of the image, which will consume less data.

Sec-CH-Width

It provides the final size of the image required as per page layout after factoring in the device pixel ratio. Sec-CH-Width hint is sent with requests for image resources fired off by <img> or <source> tags using the sizes`.

This is the most useful client hint with respect to implementing responsive images.

For example - Let’s say a page has an <img> element with the sizes attribute set to 300. This means the layout needs a 300 CSS pixel wide image.

<!-- Allow Width header to be sent -->
<meta http-equiv="Accept-CH" content="Sec-CH-Width">
...
<img src="/image.jpg"sizes="300px" />

Now the browser takes 300 and multiplies it with the device pixel ratio. Assuming device pixel ratio (DPR) is 2, browser sets Sec-CH-Width hint to 600, i.e. 300x2. This is the actual size of the image required for the current layout.

Request headers
GET: /image.jpg
sec-ch-width: 600

This allows the server to respond with an image, which is optimal for this device and the page's current layout.

Sec-CH-DPR

This hint provides the device pixel ratio. It is equivalent to window.devicePixelRatio.

For example - when opted in

<!-- Allow DPR header to be sent -->
<meta http-equiv="Accept-CH" content="Sec-CH-DPR">
...
<img src="/image.jpg" />

Assuming, device pixel ratio is 2, the browser will set the Sec-CH-DPR hint to 2.

Request headers
GET: /image.jpg
sec-ch-dpr: 2

You can use this header on the server to send the right image variant e.g. 1x, 2x or 3x based on the actual device pixel ratio.

Sec-CH-Viewport-Width

This hint provides the viewport width in CSS pixels. It is equivalent to window.innerWidth.

You can use this hint on the server-side to respond with an image that is suitable for a specific screen size. This is useful for implementing an art direction use-case.

For example - when opted in

<!-- Allow Viewport-Width header to be sent -->
<meta http-equiv="Accept-CH" content="Sec-CH-Viewport-Width">
...
<img src="/image.jpg"sizes="300px" />

Assuming, device screen width is 300 CSS pixel wide, the browser will set Sec-CH-Viewport-Width hint to 300.

Request headers
GET: /image.jpg
sec-ch-viewport-width: 300

Accept

It provides what all content type the browser supports, which can be leveraged by the server to send the most optimal response.

Example use case includes serving images in WebP or AVIF format when browser declares the support for it in Accept header. For example - Accept header value in an image resource request in Chrome is image/avif,image/webp,image/apng,image/*,*/*;q=0.8. It has image/webp in it, and the server can use this to respond with WebP format.

If you are using ImageKit, then it automatically converts image format based on Accept header value.

Save-Data

This hint indicates the client's preference for reduced data usage. When the value is on, the server should try to send an alternative smaller payload in the response. For example, in image requests, the server should respond with a lower quality image to reduce data usage if the value of Save-Data request header is on.

Save-Data Client Hint
Save-Data Client Hint

Mobile browsers such as Chrome Mobile and Opera Mobile allow the user to activate a data saver mode. With this mode enabled, the browsers send the Save-Data header with the request, with the on value. With this mode disabled, the Save-Data header is not sent at all.

For example, the right image is almost 33% smaller when accessed by a client with Save-Data enabled. You can test this on the Chrome desktop by installing this extension.

ImageKit supports Save-Data mode and when enabled, it will deliver a low-quality image to reduce data transfer.

ECT, RTT, and Downlink

Besides Save-Data hint, we also have ECT, RTT and Downlink client hints -

  • ECT - Effective Connection Type e.g. **4g, 3g, 2g, and slow-2g.
  • RTT - Round Trip Time, in milliseconds, on the application layer.
  • Downlink - Approximate downstream speed of the user’s connection in megabits per second (Mbps).

Jeremy Wagner wrote in Google Web Fundamentals -

Adaptive performance is the idea that we can adjust how we deliver resources based on the information client hints makes available to us; specifically, information surrounding the current state of the user’s network connection.

Time is the key — Taking forever to load a high-quality image on a slow network is more frustrating for your users than showing a low-quality variant that loads quickly. You can use these network client hints to calculate a score of clients network connection quality and accordingly change your logic on server to help users on the slow network have decent experience.

Limitation of client hints

Client hints look pretty amazing. They automate responsive images without major markup changes. But there are some limitations with client hints -

Client hints don’t work in all browsers

At the moment, they are only supported in Chrome and Chromium-based browsers, Edge, and Opera.

Can I Use - https://caniuse.com/client-hints-dpr-width-viewport
Can I Use - https://caniuse.com/client-hints-dpr-width-viewport

Cross-origin client hints removed in Chrome 67 in the desktop version

After client hints landing in Chrome 35, there were concerns around tracking users across multiple websites because of the device-related information passed through client hints.

Essentially, sending highly granular data, such as image and viewport width, may help identify users across multiple requests. More importantly, there was no mechanism to control which origin should receive this. The moment you opt-in for client hints, all image resource type request will start sharing device-specific data with origins.

If your website is hosted on www.example.com and images are on www.example.com/image.jpg it is fine. But if images on www.images.third-party-service.com/image.jpg have the same data, it may reveal the same information about the user to other origins that may not have had access to it before.

As a result, the Chrome team removed cross-origin client hints in the desktop version of Chrome 67, but they still work in mobile. These restrictions will be removed soon when work on Feature Policy is complete.

Your server should support client hints

For client hints to work, your server should understand them and respond with an appropriate image.

For example, the servers should be able to -

  • Deliver the right size image based on the value of the Sec-CH-Width client hint.
  • Serve images in WebP or AVIF format based on the value of the Accept client hint.
  • Select the right image variant based on the value of the Sec-CH-DPR client hint.

Fortunately, you can automate all this using an image CDN like ImageKit.io, which supports client hints out of the box.

Caching on CDN can be tricky

You would want to cache your static resources like images to accelerate the download and decrease the load on your origin server. But with client hints in the mix, it can be tricky.

Usually, a CDN looks for the objects in the cache based on the resource URL. However, when the server changes the response based on other request HTTP headers i.e. client hints, the CDN has to be aware of how to respond to future requests. Essentially if we are changing the response based on the Accept request header value, we will have to configure the CDN to store seperate response in cache based on the value of the Accept request header.

You can use the Vary response header to indicate CDN and intermediate proxies to maintain different cache objects based on header names in Vary. For example -

Vary: Accept, Sec-CH-DPR

This means that CDN and intermediate proxies should take the value of the Accept and Sec-CH-DPR request header in account in addition to resource URL while storing and looking up objects in the cache. The cache lookup key should be Accept + Sec-CH-DPR + resource URL.

It is important to note that you should not set Vary: User-agent because there could be a huge number of unique values for the User-Agent request header, resulting in a lower cache hit ratio on the CDN.

An image CDN like ImageKit.io already takes care of CDN caching while ensuring the cache hit ratio is high.

Chapter 8 - Responsive images in CSS

background-image is a very powerful CSS property that allows you to insert images on elements other than img. What you have learned so far about responsive images deals with only HTML markup. Let’s see how to implement responsive images in CSS.

We have the following use-cases to cover:

  • Art direction i.e. loading entirely different images based on screen width. In HTML, we used the picture element. In CSS, we can use old fashioned media queries.
  • Providing high-resolution image variants to let the browser make a choice. In HTML srcset attribute provides the browser with 1x, 2x, and 3x variants of the same image. In CSS, we have the option to use image-set.

In CSS media query alone is enough to implement responsive images. However, image-set is similar to srcset because it provides the options and lets the browser choose the image. But it is not equivalent to srcset.

Art direction in CSS — loading an entirely different image based on screen size

In HTML, we have a picture element to load an image conditionally.

<picture>
  <source srcset="/large.jpg"
          media="(min-width: 800px)">
  <source srcset="/small.jpg"
          media="(min-width: 400px)">
  <img src="/large.jpg" />
</picture>

In CSS, we have media queries.

.element {
  background-image: url(small.jpg);
  background-repeat: no-repeat;
  background-size: contain;
  background-position-x: center;
}

.@media (min-width: 800px) {
  .element {
    background-image: url(large.jpg);
  }
}

In this example, on small screen background-image: url(small.jpg) is applied and when viewport width is greater than or equal to 800px, background-image: url(large.jpg) is applied.

Loading high-resolution images based on device pixel ratio

In HTML we have srcset

<img src="image.jpg" 
     srcset="image.jpg 1x,
             image_2x.jpg 2x"
/>

In CSS, we can use image-set and media queries.

  1. Using image-set to provide high resolution images variants

.demo {
  background-image: url(image.jpg); // fallback
  background-image: -webkit-image-set(  
    url(image.jpg) 1x,  
    url(image_2x.jpg) 2x  
  );  
  background-image: image-set(  
    url(image.jpg) 1x,  
    url(image_2x.jpg) 2x
  );
}

If image-set is not supported, the fallback background-image: url(image.jpg) will be applied.

  1. Using media queries

.demo {
  background-image: url(image.jpg);

  // Standard syntax supported in Chrome, Firefox, and Opera
  @media (min-resolution: 2dppx), 
  (-webkit-min-device-pixel-ratio: 2)  /* For Safari & Android Browser */ 
  {
    .demo {
      background-image: url(image_2x.jpg);
    }
  }
}

Chapter 9 - Lazy load responsive images

What is Lazy loading images?

As Rahul Nanwani wrote in lazy loading images guide -

Lazy Loading Images is a set of techniques in web and application development that defer the loading of images on a page to a later point in time - when those images are actually needed, instead of loading them upfront. These techniques help in improving performance, better utilization of the device’s resources, and reducing associated costs.

Usually, to implement lazy loading in HTML, instead of src or srcset attributes, we use data-src or data-srcset so that browser does not load images during speculative parsing. Later on, when Javascript is executed, and the user has scrolled near the image element, we load the actual image and update the src or srcset attribute’s value.

Two very popular lazy loading libraries lazysizes and vanilla-lazyload support responsive images out of the box. In this guide, we will share a few examples of lazysizes.

Lazy loading responsive images in srcset and sizes

<img
    sizes="(min-width: 1000px) 930px, 90vw"
    data-srcset="small.jpg 500w,
                 medium.jpg 640w,
                 big.jpg 1024w"
    data-src="medium.jpg"
    class="lazyload" />

Using low quality placeholder in lazy loading

<img
    src="low-quaity-placeholder.jpg"
    sizes="(min-width: 1000px) 930px, 90vw"
    data-srcset="small.jpg 500w,
                 medium.jpg 640w,
                 big.jpg 1024w"
    data-src="medium.jpg"
    class="lazyload" />

Lazy loading images in picture element

<picture>
      <source
          data-srcset="500.jpg"
          media="(max-width: 500px)" />
      <source
          data-srcset="1024.jpg"
          media="(max-width: 1024px)" />
      <source
          data-srcset="1200.jpg" />
      <img src="fallback-image.jpg"
          data-src="1024.jpg"
          class="lazyload"
          alt="image with artdirection" />
</picture>

Chapter 10 - How to verify responsive image implementation?

After you implement responsive images, it's essential to verify that the right image is being loaded on different devices. Let’s discuss how various tools and browser's developer console can help us.

Using ImageKit website analyzer

ImageKit website analyzer is a tailor-made tool to find our image related issues on the webpage. It loads the webpage in a headless browser. All the image requests are monitored and compared against the optimized version fetched from ImageKit.

You will get a report like below. Look under Properly resize images to see if images are in the right sizes.

This tool also provides an image by image explanation for all images on the webpage like this:

This tool provides image analysis reports for desktop and mobile. However, at the moment, it does not take into account the device pixel ratio, so you will have to consider that, especially while looking at the mobile report.

Using Lighthouse

You can use Lighthouse to verify responsive images are implemented correctly. Run the Lighthouse Performance Audit for Mobile & Desktop and look for the Properly size images audit results.

This will show all the images that aren't appropriately sized based on the page layout.

Source - https://web.dev/uses-responsive-images/
Source - https://web.dev/uses-responsive-images/

Manually verifying responsive images

You can use the Chrome device emulator to load a webpage on different devices and see the image requests in the network tab. Few points to note before starting:

  • Browser cache should be disabled - so that with subsequent reloads, we can see which image resources are being requested.
  • Ensure device pixel ratio is added, as shown in the screenshot below.

Now open the webpage and hover over the image element to see its required CSS pixel width. Multiple it with DPR value to get the required width of the image resource.

For example here,

  • DPR value - 2.6
  • CSS pixel width - 206
  • Required image size - 206 × 2.6 = 535.6

Available options based on srcset

srcset="https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-225 225w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-300 300w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-350 350w,
        https://ik.imgkit.net/ikmedia/women-dress-1.jpg?tr=w-640 640w"

So the nearest candidate is a 640px wide image, and the browser downloads this image. You can use the same technique to see if the right size images are loaded or not.

FAQs

Why can't we do this using CSS or JavaScript?

It's logical to think that we have all the information about browser, device, and layout and can use Javascript to load the right size image. However, this will delay the loading of image resources and defeat the whole purpose of rendering images quickly. Modern browsers don’t wait for Javascript and CSS to be parsed and executed before it triggers the image request. The browser scans the whole HTML and looks for image resources without waiting for Javascript to execute. That is why we need to use the srcset, sizes, and picture element to implement responsive images. Learn how the browser loads a webpage to understand this.

Can I use both density and width descriptor in srcset?

No. As per the specifications about how srcset is parsed - If an image candidate string for an element has the width descriptor specified, all other image candidate strings for that element must also have the width descriptor specified. Also, if an element has a size attribute present, all image candidate strings for that element must have the width descriptor specified.

Why do we need sizes attribute with srcset?

So that browser can trigger image load as part of preloading instead of waiting for CSS to download & parse. Learn how the browser loads a webpage to understand this.

Why do we use width and not a height in srcset?

We have been specifying only image widths in srcset and not height because the vast majority of responsive design is width constrained and not height, so to keep things simple, the specification only deals in widths.

What sizes values should I declare?

The value of the sizes attribute should specify the size of the rendered image in CSS pixel. It could be an absolute value e.g. 250px or relative to viewport size e.g. 33vw means 33% of viewport width. You can use comma-separated media conditions in the sizes attribute to tell how rendered image width changes based on viewport size. For example -

sizes="(max-width: 300px) 100vw, (max-width: 600px) 50vw, (max-width: 900px) 33vw, 900px"

What image sizes should I provide in srcset?

First, think about your layout i.e., CSS. Then accordingly, write sizes. And finally, choose breakpoints for srcset considering different DPR values and effective image sizes you need 🙌.

How should I generate different size image assets?

You will need to provide different size image variants to implement responsive images. On a typical e-commerce or news website, this could be a huge problem as we are dealing with thousands of photos. The solution is to use an image CDN. ImageKit.io is an image CDN that provides real-time image resizing, automatic image format conversion, and optimization. It offers a forever free plan with generous bandwidth limits.

How do you make an image responsive in HTML?

If you are wondering:

  • How to load different images based on screen size?
  • How to change image size for mobile devices?
  • How to display different images on mobile and desktop devices?

Then, know that all of these use-cases are related and can be solved using the latest responsive image techniques that we have discussed in this guide.

Often you will only need srcset attribute. In few cases you might want to use picture tag.

Srcset not working

You could face one problem while using srcset in responsive images is that the browser is always using the largest image available. There could be two things:

  • If you resized the window to test responsive image implementation and the browser has once loaded a high resolution bigger image, then it won’t load a smaller image again because it can use the largest one from the cache. It saves time & bandwidth. That is the whole point. It is specific to Chrome.
  • You need to consider the DPR value into account. On the retina display, the browser will go for a wider image based on options available in srcset. If 300px wide image is required and DPR is 2, the browser needs at-least 600px wide image. It might be confusing as you were expecting it to pick a 300px wide option from srcset.

Client hints not working

After client hints landing in Chrome 35, there were concerns around tracking users across multiple websites because of the device-related information passed through client hints. As a result, the Chrome team removed cross-origin client hints in the desktop version of Chrome 67, but they still work in mobile. These restrictions will be removed soon when work on Feature Policy is complete.

How to make a background image responsive?

Setting width and height is not enough when it comes to using background images. You can leverage the latest techniques to implement responsive images in CSS e.g. image-set and well-known media queries.

Responsive images srcset vs picture?

More often than not, you will only need srcset with sizes while implementing responsive images. That is because the primary use-case of responsive images is to load the right size image on different devices e.g. mobile or desktop. However, a picture tag is there if you need to implement art direction i.e. to load an entirely different image based on available screen width or device orientation.

Picture tag not working

If the picture element is not working, ensure that you have the img element inside the picture element. This acts as a fallback when the browser doesn’t support the picture element. You can also use a polyfill for picture element.

How to make images responsive in WordPress?

Responsive images landed in WordPress 4.4, making it easier for theme developers to implement responsive images in their themes. As soon as an image is uploaded in the media library, WordPress creates multiple different size variants and store them. When you embed the image in a post, the img element has srcset and sizes.

Default sizes attribute

By default, the value of the size attribute is equivalent to -

(max-width: {{image-width}}px) 100vw, {{image-width}}px

However, as a theme developer, you have the best knowledge of ideal image size, and you should override this default, as discussed below.

Configuring responsive images for your theme

Theme developers should use wp_calculate_image_sizes helper function to create the sizes attribute for an image.

For example, the below hook will apply to all content and featured/thumbnail images. Thanks to Tim Evko for the code snippet.

function adjust_image_sizes_attr( $sizes, $size ) {
    $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px';
    return $sizes;
}
add_filter( 'wp_calculate_image_sizes', 'adjust_image_sizes_attr', 10 , 2 );

You can also hook into wp_calculate_image_srcset to calculate the image sources to include in a srcset attribute.

Responsive images in WordPress before 4.4

Before version 4.4, you have to use the RICG Responsive Images plugin. This plugin works by including all available image sizes for each image upload. Whenever WordPress outputs the image through the media uploader, or whenever a featured image is generated, those sizes will be included in the image tag via the srcset attribute. This plugin doesn’t add a sizes attribute, though.

Questions

We hope this guide helped you understand the responsive images in HTML and CSS. If you have a question, feel free to comment or email on support@imagekit.io.