Cascading Semantic Stylehooks

Yesterday Brian sent me a link to a little something they had worked on. It was a script that added a role attribute to all HTML elements with an exposed ARIA role. But why? you might ask. To feed our browser rendering engine overlords of course. No, to make it possible to use a CSS attribute selector to select elements based on how they’re exposed. Brian goes into detail in their post Reflecting on Roles.

I really like this idea because it would make writing CSS a lot easier and at the same time more meaningful. Last year I wrote a little bit about styling based on meaning in Cool Semantic Styles.

ARIA rules

ARIA does rule, but that’s not what I mean. ARIA has some rules that you should adhere to when you use it. Under Web developer requirements for use of ARIA in HTML it says (emphasis mine):

Web developers may use the ARIA role and aria-* attributes on HTML elements, in accordance with the requirements described in [wai-aria-1.1], except where these conflict with the strong native semantics or are equal to the implicit ARIA semantics of a given HTML element.

Brian’s current script breaks this rule for all the things. Which isn’t really a problem; adding a role that has the same value as the implicit role doesn’t actually break anything. The reason it’s a rule is to discourage authors to manually add them when it’s not necessary to do so—which could go wrong and then actually break things.

I would love to see something like this come of the ground as it would make styling so much easier. Brian already mentioned that a new pseudo-class might be a better solution.


Brian wrote:

Note, the syntax there is debatable - practically speaking it would probably make more sense to use a pseudo-class like :role(heading) for this purpose.

That looks pretty neat. Imagine being able to write something like this:

:role(button) {

:role(button):matches(:hover, :focus) {

That would be downright awesome! It would select all the elements that are exposed as buttons—whether it be <button> or <a href="#rofl-not-a-real-button" role="button"> and style them, no more adding a btn class to all your divs that aren’t really buttons. In all seriousness, this would both make writing CSS easier and make people more aware of accessibility (or at the very least aware of these things called element roles).

The reason a pseudo-class would work better than an attribute-selector, is, as Brian says: I mean... a pseudo-class only because it isn't really an attr, it's a property.

What are these roles anyway?

Hmm, kinda skipped over that. Right, so an element’s role defines what kind of thing the element is. For example: the <nav>-element has an implicit role of navigation. This tells a browser that the thing, the <nav>-element, is in fact a navigation section. Which in turn means that a browser can attach certain behaviour to it. A browser could implement a keyboard shortcut that looped through all the navigation elements on the page—which would be very helpful.

That’s already pretty cool, but it gets better (and more important)! Using a computer is pretty hard. Just look at how detailed the 1984 Macintosh manual was. We take ease of use for granted sometimes because we work with computers everyday and most things look pretty obvious to us.

That’s the thing though, not everyone can see, computers for example. This is where the role really shines; the accessibility tree exposes the role property of an element in a way that the computer can understand. This information can be used for loads of things: telling a screen reader user that they can activate a button or checkbox via a key combinations, telling a printer not to print the header and footer elements, or make it possible for CSS to select elements based on their role and style them (if only 😬).

If you want to learn more about roles, the accessibility tree, and how these communicate with other systems on a computer I highly recommend you read the Accessibility Object Model (AOM) explainer. AOM isn’t implemented yet, but the explainer does a great job of explaining how it all works.

Okay, now what?

Good question. Brian told me that this isn’t the first time something like this has been proposed. Which totally isn’t a recurring theme when it comes to web things. Nope, not at all.

Anyway, it would be good to explore this more and get more people in on it. First of all, the way in which this works needs to change a bit as to not “break” ARIA’s rules.

Back to classes, eh?

Perhaps, it’s what they were originally meant for anyway [citation needed]. I don’t see why Brian’s script couldn’t add a button or menuitem class to their corresponding elements (which have those roles).

That, er, sounds kinda like what Bootstrap does with its btn class and so forth. But whatever, the thing is that you would get the actual exposed role for things as a class that you can use to style.

So maybe not classes then

Ideally, to help this get a bit more traction, we would turn this into a plugin for something like PostCSS. I already asked Jonathan Neal if they’d be interested. To which they said they are! I’ll update this page when they’re finished :D. Jonathan has already made an implementation that works 🌟.

What this does is turn something like :role(heading) into all the corresponding CSS selectors, like so:

[role="heading"] {

Jonathan said they wrote it as a proof of concept and that it’s not published into the PostCSS repo. Regardless, it shows you how easy it would be to style categories of elements, or rather, elements that share the same role.

Okay, thanks for, uhm, what exactly?

As of right now nothing much. I’d like to thank Brian for coming up with this, writing the script that does all the hard work, and reviewing this article. I’d also like to thank Jonathan for writing the PostCSS plugin to demonstrate how the pseudo-class selector could work. If you also think this is neat, please tell Brian over on Twitter and tell Jonathan on Twitter if you think the PostCSS plugin is something you’d be interested in using.

Further reading

Related issue on CSS a11y TF GitHub repo (migrated from previous issue tracker, original issue is from 2013-06-26 😱)