Paul Pearson
Paul Pearson

Reputation: 85

How to fix role="tablist"/role="tab" Axe violations?

We have some tabs on our page with the following structure:

<ul role="tablist">
  <li>
    <div role="tab" tabindex="0" aria-selected="true" aria-controls="id1">First tab content...</div>
  </li>
  <li>
    <div role="tab">Second tab content...</div>
  </li>
</ul>

This gives rise to two violations when running Axe accessibility tests, namely: <li> elements must be contained in a <ul> or <ol>, and Certain ARIA roles must contain particular children (also the converse that certain children need particular parent roles).

I understand that the first violation is due to the tablist role meaning that the <ul> is no longer seen as a <ul>. I don't understand the second violation as the spec does not enforce that elements with role="tab" are immediate children of tablist.

One possible fix, which prevents these violations, would be to move role="tab" up to the <li> elements. The problem then though is a different violation: Nested interactive controls are not announced by screen readers, due to the contained <div> being focusable presumably. Changing this to the outer <li> would require a whole load of js and css changes, so is not a simple fix.

To what extent does this really need fixing and what is the best approach?

Upvotes: 4

Views: 4522

Answers (2)

slugolicious
slugolicious

Reputation: 17535

Axe is wrong. The spec for the tab role says:

Authors MUST ensure elements with role tab are contained in, or owned by, an element with the role tablist.

Emphasis mine. Notice that is says a tab must be contained in a tablist. Contained in does not mean a direct child but rather a descendent.

This is further emphasized if you look at the next phrase, or owned by. "Owned" has a definition:

An 'owned element' is any DOM descendant of the element, any element specified as a child via aria-owns, or any DOM descendant of the owned child.

So again, as long as the tab is a descendant of a tablist (child, grandchild, great-grandchild, etc), then technically it's valid and axe is wrong.

Now, the definition of "owned" gives you a clue as to how you can work around axe's bug in case the violation bothers you. Add aria-owns to your <ul> and point it to the tab elements (your <div>s). Your original code will pass axe like this:

<ul role="tablist" aria-owns="foo1 foo2">
  <li>
    <div id="foo1" role="tab" tabindex="0" aria-selected="true" aria-controls="id1">First tab content...</div>
  </li>
  <li>
    <div id="foo2" role="tab">Second tab content...</div>
  </li>
</ul>

You still have the issue of an <li> not contained in a <ul>. @graham covered that part. And I also agree with @graham that you should follow the authoring practice for the tab design pattern.

Upvotes: 9

GrahamTheDev
GrahamTheDev

Reputation: 24865

The answer with your current setup is to remove the semantic meaning from the <li>.

<ul role="tablist">
  <li role="none presentation">
    <div role="tab" tabindex="0" aria-selected="true" aria-controls="id1">First tab content...</div>
  </li>
  <li role="none presentation">
    <div role="tab">Second tab content...</div>
  </li>
</ul>

role="none presentation" removes all semantic meaning from the <li> (so think of it like a <div> now).

It would be preferable to move everything "up one level" to keep the DOM tidy though, so a better solution might be:

<ul role="tablist">
  <li role="tab" tabindex="0" aria-selected="true" aria-controls="id1">
    First tab content...
  </li>
  <li role="tab">
     Second tab content...
  </li>
</ul>

This would work as you have now changed the semantic meaning of both the <ul> and the <li>, although it might need a minor adjustment to CSS.

Also Axe can sometimes complain with the first example, even though it is valid, the second example will have no complaints from automated checkers.

Alternatively you may want to look at using <button> elements to create tabs, see the tabs example on W3 on how to structure that.

It has the advantage that the click handler can be used for both mouse and keyboard interactions because you are using a <button>.

Upvotes: 3

Related Questions