Mini-guide to add an image

by Cristian Diaz published on

Adding an image with HTML is pretty easy, it’s just a simple tag, after all, right?

<img src="path/to/image.jpg" />

But when you start taking into consideration topics such as performance, screen sizes, accessibility, pixel density, or user preferences, you might ask yourself at some point if plain HTML is enough for the task… And the answer is yes! HTML has many options and is powerful enough to handle this task. This article will cover what you should consider at the moment of adding an image to a site with HTML.

Basic considerations

alt attribute

Let’s start with a very important one: your image needs an alternative text. It’ll have screen reader users to identify the image content, but it’ll also help when the image doesn’t load (maybe because the image is not available, or maybe because the user has a slow network). Otherwise, they won’t know what the image is. This can be done with the alt attribute.

<img src="path/to/image.jpg" alt="A white cat laying on a sofa" />

Keep in mind, sometimes an image is there for purely decorative purposes, so making it visible for screen readers might not be ideal, in those cases, you can use an empty alt to make screen readers ignore this image.

<img src="path/to/image.jpg" alt="" />

What should the alternative text contain? Usually, it should contain a brief but concise description of what’s in the image. If it’s an image with important information (like a place for an event or a full infographic), this information should also be in alternative text. W3C has created an alt decision tree to help you decide what information you should add.

width and height attribute

One of the most important metrics for performance is Cumulative Layout Shift (CLS), which measures how much a website's content shifts when it’s on screen. In other words, a low CLS means the content is pretty stable and will create no problem with user interactivity. You can check more about this concept, you can check this article on web.dev about CLS.

Image loading can create layout shift issues and one way to avoid them is by using the attributes width and height for images. It’ll tell the browser the intrinsic size of the image, and it’ll create the image’s aspect ratio when the website is loading, which will reduce the layout shift it can create. This is how it’d look in an image.

<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800" />

But that’s not all we can do to improve performance on our website with an img tag!

Lazy loading

You can have a lot of images on your website, and that can affect its loading time. This is where the concept of lazy loading comes in. That means the image will start to load only when it’s on the visible part of the viewport. There was a time when this was made with JavaScript but now HTML offers us a way to do it easily by using the attribute loading="lazy"

<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800"
loading="lazy" />

When you use loading="lazy" in tandem with height and width attributes, you’ll make sure the website has enough space for the image at the moment of loading, and at the same time, you’ll not load the image unless the user needs it, which is great for performance!

Keep in mind though, if a user has JavaScript disabled, loading="lazy" won’t work, so you need to keep in mind other performance considerations, but let’s put a pin on that topic for now.

Also, keep in mind using loading="lazy" in your Largest Content Painting element (we’ll talk about this metric later) will actually increase your loading time, so be careful and always test it to know if this attribute gives the intended results.

figure and figcaption

Sometimes, you need to add additional information that wouldn’t fit in the alt, like image credits, or an important note about it that you’d prefer everybody can access to it. You can use the tag figure to wrap the img and then add a caption with the tag figcaption.

<figure>
<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800"
loading="lazy" />

<figcaption>Persian cats can be recognized by their big hair, round face, and short muzzle</figcaption>
</figure>

Keep in mind, figcaption needs to be a direct child of the figure element. Otherwise, it might not be recognized by assistive technologies. This is something Manuel highlights in his article divs are bad!.

Keeping these considerations in mind helps a lot when you create an image, but that’s just the tip of the iceberg. When you start taking into consideration multiple variables like screen sizes, image formats, and user preferences. img tag starts to fall short in power. This is where another tag comes in to add more functionality!

picture tag

This tag is a container for multiple image sources and depending on the conditions you add to them, the browser will start choosing the right file depending on multiple factors like the viewport’s size, image format, pixel density, or user preferences. It acts as a wrapper for the multiple source elements you’ll use to determine the best image.

Let’s start checking out what we can do with this element.

Supporting different image formats

Unlike .png or .jpg image formats, there are image formats like .avif, .webp, or .apng that you might want to use because they don’t have a loss in quality but have much less weight that a more supported format. However, the previously mentioned image formats have broader support. We can use the source tag to let us use more optimized image formats depending on the browser’s support. For this, we can use the attribute type in the source element to indicate the format you need to evaluate.

<picture>
<source srcset="path/to/image.avif" type="image/avif" />
<source srcset="path/to/image.webp" type="image/webp" />
<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800"
loading="lazy" />

</picture>

In this case, it’ll start checking if the browser can accept the .avif format, if it’s not the case (like Safari before version 16), it’ll check if the .webp format is supported. If it’s not, it’ll show the one in the img element. There are multiple image formats available. If you want to learn more about it, you can check MDN’s documentation about this topic.

Do you remember I mentioned loading="lazy" might not be enough for performance purposes? This will help us to consider the browser’s support to load the most optimal image possible, creating a very robust system to give the user the best image quality possible and use as less bandwidth as necessary. It’s the best of both worlds, and that’s possible with just HTML!

Responsive images

You may want to show a different image or a different part of an image depending on the viewport’s size. picture element can help us with that with the media attribute, where we can use a media query to detect the viewport’s size and add a different image when needed.

<picture>
<source srcset="path/to/image-mobile.jpg" media="(max-width: 600px)" />
<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800"
loading="lazy" />

</picture>

You can use any valid media query there, so for example orientation: portrait and orientation: landscape are valid options when you want to show a different image depending on if the viewport’s width is bigger (or smaller) than its width. There are many possibilities you can consider to give the best image to the user.

User preferences

Now, this is very convenient for performance and user experience, but this also has an important use for accessibility: we can check user preferences to help people with certain disabilities.
For example, some people might have problems like migraines that would prefer to use dark mode, and a very bright image could trigger a migraine attack, so having a version of your image that is better suited for dark mode would be ideal.

Or maybe you have a gif, and someone has disabled animations on their device. You might want to show a static image instead so their experience is not affected. How can we do that?

As I mentioned, you can use any valid media query, that includes media queries that check user preferences like prefers-color-scheme or prefers-reduced-motion. We can even mix them with logical operators to show the appropriate image. Let’s check out how it works with an example.

<picture>
<!-- Both dark mode preference and animations are disabled -->
<source srcset="path/to/image-dm.jpg"
media="(prefers-reduced-motion: reduce) and (prefers-color-scheme: dark)" />

<!-- Static image when animations are disabled -->
<source srcset="path/to/image.jpg" media="(prefers-reduced-motion: reduce)" />
<!-- Dark mode preference is checked -->
<source srcset="path/to/image-dm.gif" media="(prefers-color-scheme: dark)" />

<!-- None of those preferences is active -->
<img src="path/to/image.gif"
alt="A cat dancing"
width="1200"
height="800"
loading="lazy" />

</picture>

It’s very important to note that order matters, it’s better to add the tags with more specific media query requirements first to be sure they will not be overwritten by the ones that will appear later. Also, keep in mind you can combine all you have seen, so there are a lot of possibilities with picture and source elements.

That covers mostly everything you should consider but there are a couple of points you can take into account.

Additional considerations

Pixel density and srcset

The attribute srcset you use in the picture element has a slightly different syntax if you want to give a better image depending on the device’s pixel density. For this, we can add a list of images separated by commas and after the image’s URL, we’ll put the type of pixel density we want to support with this image. Let’s take a look at it with an example:

<picture>
<source
srcset="path/to/image-mobile.jpg 1x,
path/to/image-mobile-2x.jpg 2x,
path/to-image-mobile-3x.jpg 3x"

media="(max-width: 600px)" />

<img src="path/to/image.jpg"
alt="A white cat laying on a sofa"
width="1200"
height="800"
srcset="path/to/image-2x.jpg 2x,
path/to/image-3x.jpg 3x"

loading="lazy" />

</picture>

There is something I didn’t mention and that is the fact you can use the srcset attribute in the img element, and it works great when you want to support images depending on the device’s pixel density. Next time to want to bring better quality images for images depending on the device, you now know how to do that!

Preload image to improve LCP

Do you remember when I mentioned CLS? There is another metric called Largest Content Paint (LCP) which is a metric that measures how much it takes to load the biggest image (or text block) to render in the viewport. Commonly, this LCP is a big image. We can use other image formats to decrease the LCP’s size, but what if that’s not enough? We have the alternative of preloading the image in the head, so it'll make the image renders faster, and by extension, improve this metric. This is how it’d look in our head tag.

<head>
<link rel="preload" href="path/to/image.jpg" as="image" />
</head>

You don’t want to abuse this technique though, because otherwise, it’ll increase the page load time. Just use it for your most important image(s) and test performance accordingly to check if it’s improving those metrics.

decoding="async"

I mentioned loading="lazy" before but there is another HTML attribute we can use to optimize a website’s loading time. While loading="lazy" decides when the image will be loaded (at the moment that it is close to the viewport), we have another attribute that defines how it’ll be loaded

Normally, all our site will be loaded synchronously because the browser reads HTML line by line, so if it finds a big image, the rest of the site won’t download until the image is loaded. This is where decoding="async" enters to give a hint to the browser to try to download the image and at the same time downloading another part of the site.

<img alt="A white cat laying on a sofa"
width="1200"
height="800"
loading="lazy"
decoding="async"
/>

Using both, decoding="async" and loading="lazy" in an image, it’ll help to reduce greatly a website’s loading time, and decoding is a highly supported attribute.

Wrapping up

Adding an image to a website is easy… But things start to complicate when you start to keep in mind performance and accessibility considerations. Luckily for us, HTML can give us the proper tools to tackle those considerations with no problem at all.

I chose this topic for this Advent Calendar for a reason and it’s because it made me notice how powerful, robust, and surprisingly complex HTML is. Seeing what HTML has to offer just to bring the right image to your screen with relatively low effort was fascinating and it started me to make dig deeper into what HTML can do.

About Cristian Diaz

Cristian is a Front-End developer from Colombia. He enjoys writing about what he learns in his blog and he decided to focus his career on helping to make web content more accessible to everybody. His main areas of expertise are HTML, CSS, and Web accessibility.

Website/blog: itscrisdiaz.com/links/
GitHub: ItsCrisDiaz
LinkedIn: itscrisdiaz
Twitter: @ItsCrisDiaz
Mastodon: @ItsCrisDiaz@mastodon.social