A picture tells a thousand words.
Images for an integral part of any web or mobile app. It is a responsibility of a developer to handle the images well, otherwise performance of the page may go for a toss. That Hero image if not optimised well based on mobile/desktop, 3G/Wifi, browser compatibility, can lead to FID ( First Input Delay ) which would lead to user drop. FID is a Core Web Vital attributing to interactivity of the page, which should be <100ms.

Containing Images

Say, we need to render an image on the UI. Like a Hero Image which is fetched asynchronously, like below.

Hero image example
<img src="https://static.images/v1/hero.jpg" class="hero-img" alt="" />
.hero-img {
  max-inline-block: 100%;
  block-size: auto;

Now, let us try to understand, what is wrong with this.


CLS stands for Cumulative Layout Shift. Its value should be <0.1 and can be calculated for any of the page by using dev tools. Why the above approach would cause high CLS? Reason being as the image is fetched over the network call, the rest of the static markup will be rendered before. Browser would have gone through an expensive one-time cycle of
JS => Style => Layout => Painting => Composting.
Now, if an image is loaded, then the browser needs to make some space on the DOM for it be loaded. This would need recalculation of all the element’s positions and placing them on the UI. Hence, it causes visible jank and poor performance as bowser needs to compute the pixels and positioning leading to same cycle again. You must have noticed this behaviour while reading something, and suddenly some ads or promotional content loads up. The focus is lost, content shifts and user experience is ruined too. Thus it is vital to give sizing hints.
Now let us try and understand how we can inform the browser that something is coming up and reserve a space for it to render so that you don’t the shift.
For this, it is important to provide a width & height attribute to img tag. It will allow browser to understand and reserve the space on the viewport.

<img src="./hero.png" width="300" height="200" />

It will let aspect-ratio be maintained at 3:2.


Say, you are receiving image from the assets of size 1000px by 800px. To make sure that the image do not squish based on the available viewport or some incorrect CSS, modern browsers now accepts CSS property of aspect-ratio. It can be set as 5/4 in this case. It is still not fully compatible with all the browsers, so do check it before using it on caniuse.com


Using object-fit helps a great deal to avoid an image to go out of bounds.
Possible values of object-fit. Source : web.dev
Majorly contain & cover are used. contain leaves empty space at top & bottom, while cover crops the image at top & bottom. object-position: top center can be used with object-fit: cover to make sure top portion of the image do not get trimmed.

Image Responsiveness

So, till now we have learned that img is not just about src attribute. There is a lot more needed to be added to make sure image really confines to our view port and do not squish and go out of bounds.
Based on our learning till now, HTML & CSS for rendering should be like –

<img src="static-assets/image.png" width="640" height="360" /> 

img { 
  max-inline-size: 100%;
  block-size: auto;
  aspect-ratio: 16/9;
  object-fit: contain; 

Now, let us focus on how to make image responsive to screen size, type of device, have more control on what type of should render and have more control over it.


Not all images will be visible on the viewport at once. Some will be below / right of the fold too which will only be visible on vertical/horizontal scroll. Example, in a carousel of 5 images, the only required image to be downloaded by the browser is the first one. Rest can wait until user goes to next image.
loading='lazy' can be used in img as an attribute. Browser will only load the image when user requests for it. For hero images, loading='eager' can be used.


decoding attribute informs the browser whether a particular image is needed to be decoded synchronously or asynchronously.decoding='async' or decoding='sync'.async is used to reduce delay in other content.


srcset attribute allows to have multiple versions of the same image. It takes up comma separated image URLs followed by width or pixel density descriptor.

<div class="box">
<img src="/en-us/web/html/element/img/clock-demo-200px.png"
srcset="/en-us/web/html/element/img/clock-demo-200px.png 1x, /en-us/web/html/element/img/clock-demo-400px.png 2x">

Here, based on the pixel density descriptor, browser will decide which image to be loaded. Note, it is not a replacement of src attribute.


It becomes very powerful when used alongside srcset attribute. sizes takes up comma separated list of media queries of image widths.

srcset="/files/16870/new-york-skyline-wide.jpg 3724w, 
/files/16869/new-york-skyline-4by3.jpg 1961w, 
/files/16871/new-york-skyline-tall.jpg 1060w" 
sizes="((min-width: 50em) and (max-width: 60em)) 50em, 
((min-width: 30em) and (max-width: 50em)) 30em, 
(max-width: 30em) 20em" 

With above code, we have given charge to the browser to decide which is best image to be loaded based on the size of the image and available width.


Using picture tag is not common, but it gives a lot of power to developer on deciding what content to choose. Instead of one image being scaling up/down based on viewport, it allows multiple images to load to nicely load up in the viewport.

  <source media="(min-width:650px)" srcset="large.jpg">
  <source media="(min-width:465px)" srcset="small.jpg">
  <img src="flowers.jpg" alt="Flowers">

Above example states that, if min-width:650px meets, large.jpg will be rendered, else if min-width: 465px meets, then small.jpg with the fallback to flowers.jpg
One more usecase where it will be useful is in choosing type of image format to be rendered. Before an year ago, webp format were known to be most optimised image format, now avif format is better at performing at compression without visible loss at quality. However, browser support is not proper for both of them.

webp format browser support

webp format browser support

avif format browser support

avif format browser support

However, we do have a chance to render these on the browser which supports them with a fallback with png format.

<source srcset="img.avif" type="image/avif" />
<source srcset="img.webp" type="image/webp" />
<img src="img.png" alt="Red Flower" />

So, based on the browser’s compatibility avif / webp / png will be loaded.


Making images accessible to screen readers is really important. Using alt tags with proper messages describing the image makes it happen. Checkout this article by Jake Archibald on how to write great alt tags.
Even for presentational images, alt tag is required. Adding an empty alt tag is just fine. Screen readers get a hint that this is a presentational image if empty alt tag is present. However, ideally HTML is for content, so any presentational image should not be a part of HTML, instead be present in CSS.

So, based on our learning above, we can start using img tag with proper attributes based on the usecase.

alt='Red flower'
srcset='small.png 1x, med.png 2x, large.png 3x'
sizes='(min-width: 66em) 33vw, (min-width: 44em) 50vw'

Dev notes

  • Images form an integral part of the application, measure its load, impact through devtools lighthouse test, WPT tests, profilers.
  • Do not over do. you should know what you are optimising.
  • object-fit is really powerful. It avoids image squishing.
  • loading & decode attributes do help to reduce network load and reduce delay
  • alt tags are important. do not miss them and write meaningful message inside it.

Thanks to Shubham Shukla for constantly bugging me to write & my employer for giving 1 week break, otherwise this wouldn’t be possible. 🙂
Let me know your thoughts or more ways to do so in comments.
Happy New Year to everyone!!


Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *