Alternative Text for CSS Generated Content
Relying on images that come from CSS has always been risky from an accessibility perspective. CSS background images, in particular, must either be purely decorative or be described to the user in some way.
The risk is no different for images coming from CSS generated content using content: url(foo.gif)
(typically paired with ::before
and ::after
). Addressing it is often a bit trickier.
A long time ago using CSS generated content was a WCAG auto-fail. Today using content:
to insert plain text can be a boon for users — from browsers using it to present quote marks for rendering <q>
to authors relying on it for hints.
Too often, however, I see developers relying on images in the generated content to convey important information to users. The following construct is not uncommon:
label.required::before {
content: url(star.png);
}
For this to convey to a user that the field is required, the image has to load and the user has to be able to see it. If either is not the case, this can be a problem.
The Future
CSS Generated Content Module Level 3 (Editor’s Draft) allows an author to specify alternative text for images (referenced through url()
) or other non-text content (glyphs). Section 1.2. Alternative Text for Accessibility offers two examples, both of which involve following the non-text content with / "text"
.
Using plain text with an image:
.new::before {
content: url(./img/star.png) / "New!";
/* or a localized attribute from the DOM: attr("data-alt") */
}
Using a blank value with a strictly decorative glyph:
.expandable::before {
content: "\25BA" / "";
/* a.k.a. ► */
/* aria-expanded="false" already in DOM,
so this pseudo-element is decorative */
}
Unfortunately support is not where we need it today to rely on it. Further, for a browser that does not support the / "text"
syntax, using it will invalidate the entire declaration, preventing your image or glyph from appearing. This could be some de facto progressive enhancement (if considered carefully) or you could just repeat your code without the text alternative.
Test
I wanted to compare the current state of CSS generated content alternative text with the trusty <img alt=>
construct. Granted, authors both forget and write terrible alt text all the time, so we can expect the same with CSS. But it is a good idea to get a baseline understanding.
I am using it as plain text, as a heading, and as the accessible name for each of a link, a field, and a button. I also tried it with an emoji as the text alternative to maybe head that chaos off early.
If the embed does not work, visit the pen directly or try it in debug mode for your own testing.
See the Pen JjKoeGG by Adrian Roselli (@aardrian) on CodePen.
Results
- Windows 10
- Chrome 85
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Shows the broken image icon and itsalt
, without reserving space for the missing image.
- Firefox 81
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Does not show a broken image icon, but does show itsalt
.
- CSS: The presence of
- Internet Explorer 11
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Reserves the space for the image, restricting thealt
to the area and clipping as necessary.
- CSS: The presence of
- Chrome 85
- macOS 10.15.6 Catalina
- Safari 14
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Reserves the space for the image, restricting thealt
to the area and clipping as necessary.
- CSS: The presence of
- Chrome 85
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Shows the broken image icon and itsalt
, without reserving space for the missing image.
- Firefox 81
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Does not displayalt
, unless it is an emoji.
- CSS: The presence of
- Safari 14
- macOS 11.0.1 Big Sur
- Safari 14
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Reserves the space for the image, restricting thealt
to the area and clipping as necessary.
- CSS: The presence of
- Safari 14
- Android 11
- Chrome 85
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Shows the broken image icon and itsalt
, without reserving space for the missing image; emojialt
text is not announced.
- Firefox 81
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Does not show a broken image icon, but does show itsalt
; emojialt
text is not announced.
- CSS: The presence of
- Chrome 85
- iOS 14
- Safari
- CSS: The presence of
/ "alternative"
invalidates the entire declaration, so no image appears. - CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. <img>
: Reserves the space for the image, restricting thealt
to the area and clipping as necessary; SVGs not announced except on link and button.
- CSS: The presence of
- Safari
Takeaways
If you want to find a solution that works for each major platform, then do not use the CSS approach yet. Stick with images.
If you don’t care about iOS users, then Chrome is the only cross-platform browser that will work today, though that includes Chromium-based browsers (Edge, Opera, Brave, etc).
In supporting browsers, if your CSS generated content image does not load, its alternative text will not display. With the <img>
element there is a good chance the alt
will display.
CSS generated content will not localize easily. In the future, attr("data-alt")
can potentially get around that unless you rely on automated translation tools. If you need your content to auto-translate, then the CSS approach is not for you.
No matter which of the two techniques you end up targeting for your users, avoid emoji as your alternative text.
5 Comments
Very nicely analyzed as usual, thank you!
I’m starting to see testing feedback where decorative elements added via css pseudo elements — to do things like put arrows in buttons — are being read by VoiceOver for MacOS, and then getting flagged because they’re in the reading order.
In response to .Andrew, I am not sure what you mean by “flagged”, but those would not be a WCAG failure. Just a frustrating user experience. Though all the more reason not to rely on CSS generated content.
In response to .Ok – here’s what I’ve found. If an :after element contains an SVG, encoded in CSS like
content: url("data:image/svg+xml [...]");
, VoiceOver in Chrome on the Mac definitely reads it, and announces it as an unlabelled image, which is a critical WCAG issue.If the :after contains a character, VO reads the character. (Not consistently, mind you — as you noted above, the performance means you should neither count on VO accessing nor ignoring pseudo-element content.)
At the moment, icon fonts that use Unicode characters that aren’t known to the browser seem to be ignored by VoiceOver, which suggests that’s the best way to add visual decoration to an element whose purpose is otherwise clear in context.
In response to .Does that SVG have a any of
aria-hidden="true"
,<title>
,aria-label
, or similar? If not, then yeah, that is a 1.1.1 failure. It would be a 1.1.1 failure if embedded inline as well, so that issue is more about an inaccessible SVG than a 1.3.1 issue.I am not suggesting you said it was a 1.3.1 issue, but that was where my head was at based on the old F87 guidance.
If it has one or all those nodes/attributes and still gets read that way by VO, then that would be a VO bug.
Leave a Reply to Mitchell Evan Cancel response