Be Wary of Nesting Roles

Screen capture of a button element with a hyperlink nested inside. Button text is “LOL” and link text is “wut?” As a web developer, you may take it for granted that you cannot nest a hyperlink. I mean, you can nest a hyperlink, but more likely than not you already know how problematic that can be — and not just because the validator will kick that back as an error.

A little less obvious to some is that you cannot nest a button, specifically the <button> element. The HTML specification is clear on this when describing the content model for the <button> element: there must be no interactive content descendant.

The <a> element is a little more explicit, specifically noting that <a> cannot also be a child of itself: there must be no interactive content or a element descendants.

What Is Interactive Content?

Conveniently, the HTML specification provides a handy list of interactive elements (which cannot live in <a> nor <button> nor each other):

Interactive content is content that is specifically intended for user interaction.

Can You Tab to It?

But wait. There is more. You can make something interactive with this one simple, potentially problematic, trick:

The tabindex attribute can also make any element into interactive content.

But you would never put tabindex on an element for no reason, right? That would be silly.

Polyfills FOR THE W…oah.

Sometimes the application of polyfills (or even some libraries) can have an unintended consequence.

This is not new information, as evidenced by a 2012 post that discusses polyfills to make <details> (an interactive element) and <summary> (not an interactive element) behave as promised on the tin.

Go ahead and use details and summary (with an appropriate polyfill). If you must have one or more links nested in the summary, arrange them so that they are not the first things within the summary element. And even if you manage this, you should recognise that:

  • these links still won’t be in any way clearly identifiable to users of VoiceOver in Safari;
  • these links still won’t be available to JAWS in FF or Window-Eyes in both FF and IE when these screen readers are in Browse Mode; and,
  • JAWS users with IE will have a less than ideal experience reading the summary‘s content.

The thing is, how many developers really look at the final, rendered HTML once a polyfill is done chewing through the markup? How many understand just what is going on? How many test the output in assistive technology?

But it does not end there…

And Now the ARIA

Often when you see tabindex in use, it is paired with an ARIA role. Usually that is done to convert a div or span into a button (via role="button"). A terrible, Frankenstein’s monster of a button. Often a polyfill that allows a user to click something follows the same process.

The problem is that not all developers understand that throwing a role="button" onto an element turns it into interactive content (much like adding a tabindex).

We already know this block of code is clearly wrong:

<button>
     Foo
     <button>
          Bar
     </button>
</button>

But this block may not be an obvious problem to many developers:

<div role="button">
     Foo
     <div role="button">
          Bar
     </div>
</div>

Unfortunately, an HTML checker will not flag that as an error.

Bear in mind that while I am talking about buttons, it is not limited to just buttons. Here is an example that is probably more common:

<div role="button">
     LOL
     <a href="index.html">
          wut?
     </a>
</div>

What to Do

Know what the code is doing and how it affects users. The validator won’t save you, and if you are not a regular user of assistive technology you may miss what is happening.

In the meantime, Steve Faulkner has just opened an issue against the ARIA spec: define nesting conformance requirements for roles when used in HTML #54

If we can get that sorted, then we can file an issue against the validator as it will now have rules to follow.

2 Comments

Reply

Thanks for this explanation. I’ve been using some divs that work kind of like buttons but have the behaviour more like a checkbox (so I have given them role=”checkbox”), but some also have nested <a href rel=”nofollow ugc> links within the text in them. They have been working fine across everything I have tested them on, until I had to make sure they worked in JAWS screen reader. JAWS doesn’t recognise the focus on the nested <a href rel=”nofollow ugc”> tags even when tabbing and the dotted rectangle appears around the <a href rel=”nofollow ugc”> tags, JAWS is somehow forcing the parent div to receive focus (even in the console log – it is showing the parent div has focus when the dotted rectangle is around the nested <a href rel=”nofollow ugc”> tag). Your explanation about these rules makes me think I have just gotten away with doing the wrong thing, until JAWS laid down the law. I think I’ll have to float my <a href rel=”nofollow ugc”> tags just after each div/checkbox and use some css to put them back where I want them. Luckily my <a href rel=”nofollow ugc”> links always appear at the end of each div, so the screen reading should still flow the same way.

In response to Ben. Reply

Ben, in a vacuum I would caution against using a <div> to simulate a button or checkbox. Given how easy it is to style either native control to look however you want, I recommend you re-visit your pattern to see if you can use native controls (Under-Engineered Custom Radio Buttons and Checkboxen and Under-Engineered Toggles Too). An advantage is that the HTML validator would have flagged your nesting as invalid (it does not do that with roles).

Then consider leaving your links out of the button / checkbox completely while using CSS to make them appear to be a contiguous design element. See the example under the Additional Controls heading in my Block Links, Cards, Clickable Regions, Rows, Etc. post.

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>