
Alternative Text in the CSS content Property

As developers, we strive (or at least we should strive) to create websites and applications that are accessible to everyone. In front‑end development that's a primary focus on development in HTML, and often JavaScript components, but there are also accessibility considerations that need to be taken into account when working with CSS.
One of these aspects that I've been experimenting with over the past few weeks is how to include contextual alternative text when using the content property to insert graphical elements into the page, especially when combined with :before or :after pseudo‑elements to create generated content.
Why Does It Matter?
This is important because although content added to the page in this fashion should be accessible to everybody, screenreaders will generally ignore CSS content, as it is typically used for decorative purposes rather than conveying meaningful information. However, assistive technology can be configured in many different ways; during user testing for the John Lewis website we found that some configurations of JAWS would still attempt to 'read' this content.
For example, if you consider a navigation item with an arrow inserted after it, it might be coded something like this:
.link { &:after { content: '▶'; }}And used in the markup like this:
<a class="link" href="/results/"> See results</a>In our tests, we found that some assistive technology would simply announce See results whilst others would announce See results black right‑pointing pointer.
Not ideal.
The Solution
Obviously, we want to offer our users a consistent and accessible experience, which is where the often‑overlooked finer points of Alternative Text for Accessibility in the CSS3 spec come into play.
With this, we are able to pass a second field into the content property after the <content‑list>, separated using a forward slash (/):
Going back to our original example, we can use it like this:
.link { &:before { // may be announced as the unicode name: 'Black right-pointing pointer' // followed by the element's inner text content: '▶'; // may be announced as 'Alternative text', followed by the element's inner text content: '▶' / 'Alternative text'; // the generated content will not be announced at all content: '▶' / ''; }}At the moment, browser support is pretty poor, but this will improve as browser development continues.
In the meantime, an alternative option would be to simply use aria attributes to pass the same contextual information to assistive technologies.
A More Scalable Approach
As anybody who's worked on even a medium‑sized front‑end codebase will tell you: hardcoding text into the CSS in this manner is not scalable, would be difficult to maintain, and would especially raise issues around internationalisation and localisation.
As far as I can see, there could be two different options to improve this:
Use CSS Variables
We could define the alternative texts as global CSS variables, and then pass those into the second field in content; something like:
:root { --black-arrow-alt: 'Alternative text';}.link { &:before { content: '▶' / var(--black-arrow-alt); }}Use data‑* attributes
Another alternative could be to use the browser's ability to pass attribute data into the CSS via the attr function. In this way, internationalisation would only need to take place at the template level, outputting markup a little like this:
<a class="link" href="/results/" data-icon-alt="Alternative text"> See results</a>And then access this via the CSS like this:
.link { &:before { content: '▶' / attr(data-icon-alt; }}At the time of writing, Chrome and VoiceOver don't work with the attribute approach, but as mentioned above: this will inevitably come with time.
Feature Detection
From playing around, I've found that some browsers that do not like this format within content at all, Internet Explorer in particular simply not displaying the content if a slash is included.
Usage of this can be extended by using @supports to ensure that only those browsers who are able to process it, do so:
@supports (content: 'x' / 'y') { .link { &:before { content: '▶' / 'Alternative text'; } }}@supports not (content: 'x' / 'y') { .link { &:before { // We simply do not include the alternative text for // browsers that cannot support it content: '▶'; } }}Wrapping up
To summarise, there are some really cool options coming to allow us to pass more contextual alternative text, for generated content, to assistive technology. At the moment support is patchy and inconsistent, but that is changing every day.
For now, the most reliable approach is to fall back to aria attributes, but there's no harm in using feature detection to introduce these features if your user base and ecosystem allow for it.
Categories:
Related Articles

Prepending PHP to a Page in Gatsby. 
Specificity in CSS. Specificity in CSS

Disabling Text Selection Highlighting with CSS. Disabling Text Selection Highlighting with CSS

Some of the Most‑Misunderstood Properties in CSS. Some of the Most‑Misunderstood Properties in CSS

Mastering CSS for Freelance Web Development Projects. Mastering CSS for Freelance Web Development Projects

Understanding and Using Flexbox in CSS. Understanding and Using Flexbox in CSS

Fundamentals of HTML: A Guide. Fundamentals of HTML: A Guide

LocalStorage in JavaScript. localStoragein JavaScript
How to Use and Clear the CSS float Property. How to Use and Clear the CSS
floatProperty
The will‑change Property in CSS. The
will‑changeProperty in CSS
Improve Page Performance with content‑visibility. Improve Page Performance with
content‑visibilitySetting CSS Blur Filter to Zero on a Retina Screen. Setting CSS Blur Filter to Zero on a Retina Screen