
Accessible CSS Generated Content
CSS generated content can have unintended side-consequences. As Andy Clarke discovered recently, data-
attributes he used as a way to transfer content into CSS for visual purposes are not translated using built-in browser functionality.
His1
goal was to provide text effects by layering different styles of the same font on top of each other. Designers use this technique to extend a base font style with other flourishes of the same font. The final code he came up with is this HTML, using the notranslate
class to prevent translation of the heading2
:
<h1 class="type-family-jakob notranslate" data-heading="France-Norvège">
France-Norvège
</h1>
The CSS looks like this:
[class*="type-family"] { position: relative; }
[class*="type-family"]:before, [class*="type-family"]:after {
content: attr(data-heading);
position: absolute;
z-index: 1;
overflow: hidden;
left: 0;
top: 0;
font-size: inherit;
font-weight: normal;
}
Until a couple of years ago, the browsers did not treat generated content as “real” content. They did not expose it to assistive technologies like screen readers. That created an issue, for example when web developers used CSS to specify the file format of a document:
<a href="link/to/an/interesting.pdf">
Get the Report
</a>
a[href$="pdf"]:after { content: " (PDF)"; }
For visual users, the link text “Get the Report (PDF)” would have been clear. But “(PDF)” was not conveyed to screen readers and other assistive technologies and thus not their users. Most developers don’t intend that behavior, so browsers started to treat generated content as actual content.
Back to Andy’s example. The “accessible name” (the label that assistive technology uses) of his heading rank 1 would include the :before
and the :after
version of the contentand the content of the <h1>
itself. The browser “sees”:
<h1 class="type-family-jakob notranslate">
France-Norvège France-Norvège France-Norvège
</h1>
There is an ARIA attribute that we can use to overwrite3
the content of the <h1>
: aria-label
. So we could augment Andy’s code like this to avoid tripling the heading:
<h1 class="type-family-jakob notranslate"
data-heading="France-Norvège"
aria-label="France-Norvège">
France-Norvège
</h1>
This would work, but having two attributes repeating the content of the heading feels wrong. As attr()
can use any attribute, not only data-
attributes4
, we can use it with the aria-label
and remove data-heading
:
<h1 class="type-family-jakob notranslate"
aria-label="France-Norvège">
France-Norvège
</h1>
[class*="type-family"]:before, [class*="type-family"]:after {
content: attr(aria-label);
…
}
This is where this story could end. And indeed this is where this story would have ended a couple of weeks ago. Yet, thanks to accessibility advocates, aria-label
is now actually one of the attributes that istranslated when using Google Translate through Google Chrome – but not in other browsers. If that is enough to remove the notranslate
class from the heading depends on your circumstances. As the short clip of this example codepen shows, the aria-label
attribute translates nicely:
- Note that this is using Andy’s use case as an example. He immediately added a note to his article when I made him aware of the accessibility implications. I wanted to write this up as a lesson in how the web changes and how different aspects of the web can work together. After all the web is constantly evolving and we’re all learning all the time. ↩
- Usually I would oppose to remove the ability for users to translate content to their language. In some circumstances, like proper names, this can be OK. ↩
-
A note, because I see that in accessibility assessments all the time: If you use
aria-label
, the content of your element is not revealed to assistive technologies. Writing<a href="…" aria-label="opens in a new window">Visit our partner site</a>
will result in a link that only says “opens in a new window”. Just don’t use it that way. OK? ↩ -
Indeed,
attr()
was invented long beforedata-
attributes were a thing. ↩
Comments & Webmentions