Accessible CSS Generated Content

CSS generated content can have unintended side-consequences. As Andy Clarke discovered recentlydata- 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:

ARIA label translates in Google Chrome [no sound]
  1. 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.
  2. 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.
  3. 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?
  4. Indeed, attr() was invented long before data- attributes were a thing.

Comments & Webmentions

Preferences (beta)

Select a Theme
Font Settings

Preferences are saved on your computer and never transmitted to the server.