Proper websites, done properly

Accessible text replacement in CSS

5 minute read time / 580 words

Sometimes you don't need to use an <img> to get an image, and sometimes it's better not to.

In my experience, the historical issue with using <img>s is threefold:

  • They're not very flexible across different screen sizes
  • You end up setting attributes on them and overriding it all with CSS after
  • It might not be the most accessible way to get information across.

In rebuke, you could however argue that

  • Using a <picture> element would allow you to define images (at different sizes or in different shapes) for different screen sizes.
  • There's things like object-fit and aspect-ratio we can use now to help us deal with how annoying image elements can be.
  • Images can be accessible - providing you fill in reasonable and understandable alt attributes to help screenreader users.

However, <picture> elements cause more hassle than they're worth in my opinion. You have to configure a set of images for the browser to choose from based on screen size. Great. But, you have to specify breakpoints in your HTML for it to work. So if you change your breakpoints in your CSS at some point, which has the waterfall effect of meaning you need to change those inline media queries in your HTML, you've got a big find/replace job to do.

Newer CSS properties are great, but they still don't get around the picture element problem. And sure, while an image element can be accessible for screenreaders, it will announce it's an image before the alt attribute is read to describe the image.

In some cases, it's nicer and cleaner for us to provide plain text content to the browser, and have our CSS do the clever parts for us. The basic text replacement technique I use is this:

<div class="replace-me">This is some plain HTML text content</div>
.replace-me {
    inline-size: 200px;
    block-size: 100px;
    text-indent: 115%;
    overflow: hidden;
    white-space: nowrap;
    background: transparent url(/img/replace-me.png) no-repeat 50% 50% / contain;
}

Old school text-replacement techniques were similar but instead of pushing text forwards, they pulled it back out of the rectangle using something like text-indent: -99999999px;. This causes performance and visual issues. Because the browser is still rendering the hidden content, you get huge invisible rectangles drawn by the browser. The visual issues arise when you're using a screen that contains enough pixels that the text indent would not be enough and you'd get random bits of text appear on the left of the screen.

This technique employs sensible alternatives to the old method:

  • text-indent: 115%; pushes content to the right (in LTR languages) so it's always hidden just out of sight.
  • overflow: hidden; hides any sort of content that doesn't fit inside the viewport of the element's set width and height.
  • white-space: nowrap; prevents text content from automatically flowing onto a new line to ensure it's pushed outside the viewport.

The great thing about this technique is that a screenreader will just read aloud the HTML text content, but visually you'll see whatever image you want instead. This also means you can easily use your CSS breakpoints to change the shape, or image used easily and without having to add it into your HTML document. It's also got the added bonus that if you write a print stylesheet, it gives you the opportunity to either not replace the text and allow printers to just print the plain text, or you can provide a printer-friendly version of the image - say in just black and white to save on colour ink usage.