CSS can help improve your HTML⁉ - Ep 1: alt attributes
Tags: css, html, a11y, tutorial
Published: 2021-12-11 18:16:28

CSS, when used properly, can make your HTML better!

In this mini series I will be using CSS to enforce using the correct HTML elements, required attributes for accessibility etc. within our HTML.

There are two aspects to this.

The first is that we structure our CSS so that using the wrong element or attributes is impossible and styles will not work if your HTML is wrong!

The second part of this is that we add a stylesheet to our projects in development mode to help pick up on errors quickly, and then remove it for production.

As an easy introduction to this concept we will utilise the second method (a development error checker stylesheet) and explore how we can use CSS to ensure that we always remember our alt attributes (but also allow for decorative images where alt attributes **should** be left blank!)

## 1. Identifying missing alt attributes.
There are two things we are going to use as a first step. 

First we need to identify any images where the `alt` attribute is entirely missing.

``` css
img:not([alt])
```

What this effectively says is:
- `img` - Select all images
- `:not(` - Where the following is not applicable
- `[alt]` - there is not an attribute (attribute is denoted by square braces `[]`) with the name of `alt` (the bit within the square braces is the attribute we are selecting).

This now let's us apply styling to any images (`img`) that do not (`:not`) have an attribute (`[attribute name]`) of `alt`.

The other issue we need to identify is when the alt attribute is present, but someone has not filled it in.

``` css
img[alt=""]
``` 

What this effectively says is:
- `img` - Select all images
- `[]` - with the attribute
- `alt` - alt attribute
- `=""` - is not filled in (blank).

The final part will also pick up on alt attributes that are null so it will match `alt` and `alt=""`.

### Example
{% codepen https://codepen.io/inhuofficial/pen/LYjdMzy default-tab=result, html, css %}

## Content Management System (CMS) defaults
In the above example you may have noticed an issue.

Some CMSs add some default alt text that should be replaced.

`<img alt="Alt Text">` for example.

With our current solution these would pass our test, even though that alt text is completely useless!

Now you would have to adjust this for your CMS, but the principle is straight forward:

``` css
img[alt="Alt Text"]
```

We just look for any `img` element that has the exact alt text of "Alt Text" (which you would replace with the default output of your CMS).

## But what about decorative images?
While our empty alt text check may be a good start we do occasionally want to leave the `alt` attribute empty **on purpose**.

By adding an empty alt attribute we are signalling to Assistive Technology, such as a screen reader, that "this image is decorative and should not be announced".

So how can we identify these images in our HTML?

### Enter `role="presentation none"`
If you are newer to HTML you may not have learned about WAI-ARIA yet.

WAI-ARIA is a way that we can provide additional information to assistive technology so that it makes the page make more sense.

A prime example would be a screen reader. This is a piece of software that converts your HTML into output (usually spoken text) so someone who is blind (for example) can still use your website.

This is what your `alt` attribute is for, describing an image to assistive tech and people who have images switched off.

But as I mentioned earlier, sometimes we have an image that adds no value to the page (a background image for example) that we do not want announced.

While an empty `alt` attribute will ensure it is not announced I find it a good practice to add `role="presentation"` to the `img` to ensure the image is not announced.

**Let me explain a little:** a lot of elements have semantic meaning (an `<img>` element is announced as an image, a `<section>` element is announced as a section so different parts of the page make sense to Screen readers etc.).

`role="presentation"` tells assistive tech "hey, this element has no semantic meaning or significance, treat it like a `<div>`" and so it does not announce the element type etc.

So it adds an extra layer of safety as if someone accidentally added some text within the `alt` attribute on a decorative image it will still not announce anything (as you cannot have an `alt` attribute on a `<div>` essentially).

Additionally there is an improvement to WAI-ARIA where you should add `role="none"` in the future instead of `role="presentation"` as it is far more clear that you are removing the semantic information by saying "none" instead of "presentation" (they do the same thing though, they are synonyms).

Unfortunately support is not great for `role="none"` yet and we are at a change over point, so the recommended advice is to use `role="presentation none"` for maximum compatibility and to future proof your HTML at the moment.

## Bringing it all together
So we need a solution that:
- finds missing alt attributes
- finds empty alt attributes
- finds alt attributes with a certain value ("Alt Text" in our example)
- ignores missing alt attributes on items with a `role` containing a combination of "presentation" and or "none".

### Final CSS selector and example
Out final CSS selector looks like this:

``` css
img:not([alt]),
img[alt="Alt Text"],
img[alt=""]:not([role="none presentation"]):not([role="presentation"])
```

So we have our first selector to check the `alt` attribute exists on an image (as it always should even if it is null or empty!).

Then we have a second check (separated by a comma to denote a new selector) for our CMS output to remind us to change it.

Then we have our third check which will select anything with an empty alt attribute, provided it does not have a role of "presentation" or a role of "presentation none".

Note that this will still fail on `role="none"` as support is not great and `role="none presentation"` as this doesn't actually fall back to presentation and would not work in assistive tech that does not support `role="none"`. 

This is exactly what we need!

### An Example
In the following CodePen I have set up a few tests with their expected states above them. As you can see our selector works as expected! yey!

{% codepen https://codepen.io/inhuofficial/pen/RwZMvdb default-tab=result, html, css %}

## Can we go further?
Yes, of course we can!

We can also pick up bad practices with the actual content of the alt attributes!

What do I mean?

Well if you write alt descriptions that start "image of" or "picture of" they are not useful and can be very annoying for assistive tech users (remember that they already know it is an image because we used an `<img>` element that announces itself as an image!).

CSS has a limited form of regular expressions we can use to achieve this!

We can check for "starts with" using `^=`.

So we can use an attribute selector like:

``` css
img[alt^="Image of"]
```

To find all alt attributes (`[alt`) that start with (`^=`) the text "Image of" (`"Image of"`).

However this has a problem!

If we had "image of" it would not match as selectors are **case sensitive** by default and "Image of" is not the same as "image of" as far as the selector is concerned!.

Luckily we have one more trick up our sleeve!

We can add " i" to our attribute selector to make it case insensitive (notice the space before the letter i!)!

So our final selector becomes:
``` css
img[alt^="image of" i],
img[alt^="picture of" i]
```

And now we can highlight any items with poor alt attributes too! (obviously there are more examples of poor phrasing that those shown here!)


### Final example
Here is the final example where we have added two more tests to the bottom that should show in an orange dotted outline due to the alt text starting with "image of" and "picture of".

{% codepen https://codepen.io/inhuofficial/pen/dyVpxOE default-tab=result, html, css %}

### Checking for other attributes!
If you haven't heard about [Core Web Vitals](https://web.dev/vitals/) yet, they are 3 metrics used by Google to help assess the quality / speed of your page.

One of these is called [**Cumulative Layout Shift**](https://web.dev/cls/).

In essence it measures how much the page "jumps around" while loading, which can annoy users and result in unintended clicks etc.

One big cause of layout shifts is images. 

To avoid layout shifts modern browsers can allocate the space an image with take up on a page before it has loaded, provided you either use `aspect-ratio` in your CSS or give your image a `width` and `height` attribute (which is generally better if you have multiple image shapes and sizes).

So to check our image has width and height attributes we can use:

```css
img:not([width]),
img:not([height])
```

This is the same principle as checking for an entirely missing `alt` attribute we did earlier. 

I would add this as a "warning" unless you specifically state that you want all images to have a width and height attribute in your internal development guide!

#### `longdesc`
A big thanks to Alvaro for reminding me of this attribute, go check out their stuff it is awesome!

{% user alvaromontoro %}

Checked out Alvaro's stuff? Great, let's get back to it!

One other thing we can do is check for deprecated / poorly supported attributes such as [`longdesc`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/longDesc) 

This time we want to do the reverse of our previous checks for whether an attribute is missing (we want to see if it exists to throw an error).

So we can use:

``` css
img[longdesc]
``` 

and add a warning (as the `longdesc` won't do any harm provided you have a good `alt` description already).

## Conclusion
This style sheet is a useful way of enforcing image best practices.

As with anything this is not without flaws.

It doesn't work for people who have vision impairments for example.

However it is good as **part** of your development toolkit if you do not have vision impairments and want a quick and dirty way to see if you have remembered your alt attributes / done them properly!

In the next part of this series we will cover using CSS to make sure that poor HTML habits do not continue (such as using `<div role="button"`) by making it that only the correct element will allow styling to be applied to it!

## Thanks for Reading!
Thanks for reading, I hope you enjoyed it!

In fact to show my appreciation please do have a virtual ❤ and 🦄 to show how much it means to me!

I hope you enjoy the rest of your weekend!