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.
Updated Test (10 December 2021)
Following a conversation on Twitter, I updated the text to include Apple’s proprietary alt
CSS property using a fallback method. The tests below have been updated to reflect current support at the time of this writing.
Results
- Windows 10/11
- Chrome 85
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders. With the exception of links, where JAWS 2020 only announces “graphic” without its alt.
- 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.
- Chrome 96
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders. With the exception of links, where JAWS 2020 only announces “graphic” without its alt.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <img>
: Shows the broken image icon and itsalt
, without reserving space for the missing image.
- Chrome 102, 118
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders. With the exception of links, where JAWS 2022, 2023 only announces “graphic” without its alt.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <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
- Firefox 95
- 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. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <img>
: Does not show a broken image icon, but does show itsalt
.
- CSS: The presence of
- Firefox 101, 118
- 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. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <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
- macOS 12.0.1 Montery, macOS 14.0 Sonoma
- Safari 15, Safari 17
- 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. - Partially works with proprietary Apple CSS property (
alt
); VoiceOver announces alt text for links and buttons by default. When used in a label, tabbing to a field announces alt text “and one more item”, hiding the visible text. When used in plain text, alt text is only exposed when moving to previous or next or reading all (and then announced with “image”). <img>
: Reserves the space for the image, restricting thealt
to the area and clipping as necessary.
- CSS: The presence of
- Safari 15, Safari 17
- macOS 14.4.1 Sonoma
- Safari 17.4.1
- CSS: Handles images fine, but does not announce the alternative text except on field and button. Shows the alternative visually only when VoiceOver is activated and then resizes the broken image to a square much larger than it was.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. - Partially works with proprietary Apple CSS property (
alt
); VoiceOver announces alt text for buttons (but not links, a departure from prior versions) by default. When used in a label, tabbing to a field announces alt text, field label, alt text again, “and one more item”. When used in plain text, alt text is not exposed. <img>
: Does not show thealt
and may not show the broken image icon if the text height is too small.
- Safari 17.4.1
- Android 11
- Chrome 85
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders; emoji alt text is not announced.
- 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.
- Chrome 96
- CSS: Handles images and their alternative text from CSS generated content just fine, whether or not the image renders; emoji alt text is not announced.
- CSS with
attr()
: The presence of/ attr("data-alt")
invalidates the entire declaration, so no image appears. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <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
- Firefox 95
- 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. - Ignores proprietary Apple CSS property (
alt
); handles rest of CSS as above. <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
- iOS 15.1
- 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. - Works with proprietary Apple CSS property (
alt
). <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.
Revised Takeaway (10 December 2021)
Even using Apple’s proprietary property with a fallback, with Apple’s quirky support in macOS 12 / Safari 15, Firefox’s complete lack of any support, Blink/Chromium’s other quirks, and TalkBack’s refusal to announce emoji in general, I reiterate my opening recommendation – stick with images.
Update: 25 April 2024
Chrome honors the alt text in images from CSS generated content and lets it contribute to the accName. If the image reference is broken, while the alternative text still contributes to the accName, the text does not display visually. Which means immediate 2.5.3 Label in Name failure.
Firefox still doesn’t support alternative text for CSS generated content.
Safari also honors the alt text in images from CSS generated content and lets it contribute to the accName. If the image reference is broken, while the alternative text still contributes to the accName, the text does not consistently display visually. The user may need to perform some trickery, like launching (or closing) VoiceOver. Doing so makes Safari’s <img alt>
logic kick in (I am guessing here). That logic is problematic because it hides the alternative text completely if it is longer than the placeholder image or if the image is under 56 pixels in height.
Re-revised Takeaway
Don’t rely on alternative text on CSS generated content, especially for any kind of interactive control. Stick with HTML <img>
.
Martin Underhill touched on this a bit last month in Alt text for CSS generated content.
Podcast Reference (Added 7 July 2024)
This post was mentioned in the Working Draft Podcast (or at least I think it was; I don’t speak German).
YouTube: Revision 612: Neues in der Web-Plattform, Teil 2 | Working Draft.
7 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.
Is there a method to indicate CSS-generated content as decorative and to be ignored by screen readers?
In response to .The point of CSS generated content is to drop it in into the current node. So you would have to hide the entire node or create an
aria-hidden
node just to hold the generated content. Which seems weird.
Leave a Comment or Response