w3c / aria-practices

WAI-ARIA Authoring Practices Guide (APG)
https://www.w3.org/wai/aria/apg/
Other
1.2k stars 333 forks source link

Appropriate DOM structures for tabs widgets #2310

Open zelliott opened 2 years ago

zelliott commented 2 years ago

Most tab widgets seem to have the following DOM structure (note that throughout these examples some important DOM attributes are omitted for brevity):

<div role="tablist">
  <div role="tab" aria-selected="true" aria-controls="first">First</div>
  <div role="tab" aria-selected="false" aria-controls="second">Second</div>
</div>
<div role="tabpanel" id="first">First panel</div>
<div role="tabpanel" id="second" class="is-hidden">Second panel</div>

Note that the first tab is selected, and thus the second panel is hidden from the page with the .is-hidden class (which applies something like display: none to the element).

In my case, it's difficult to render multiple tabpanel elements. Instead, the content within the single tabpanel element dynamically updates based upon the selected tab. Thus, I'm wondering if there are any accessibility concerns with either of the following two alternative DOM structures:

Alternative 1

When first tab is selected:

<div role="tablist">
  <div role="tab" aria-selected="true" aria-controls="my-panel">First</div>
  <div role="tab" aria-selected="false">Second</div>
</div>
<div role="tabpanel" id="my-panel">First panel</div>

When second tab is selected:

<div role="tablist">
  <div role="tab" aria-selected="false">First</div>
  <div role="tab" aria-selected="true" aria-controls="my-panel">Second</div>
</div>
<div role="tabpanel" id="my-panel">Second panel</div>

Alternative 2

When first tab is selected:

<div role="tablist">
  <div role="tab" aria-selected="true" aria-controls="my-panel">First</div>
  <div role="tab" aria-selected="false" aria-controls="my-panel">Second</div>
</div>
<div role="tabpanel" id="my-panel">First panel</div>

When second tab is selected:

<div role="tablist">
  <div role="tab" aria-selected="false" aria-controls="my-panel">First</div>
  <div role="tab" aria-selected="true" aria-controls="my-panel">Second</div>
</div>
<div role="tabpanel" id="my-panel">Second panel</div>

Alternative 2 is what I have today, but it was brought to my attention that it could be confusing for an unselected tab's aria-controls to point to the active tabpanel. This seemed like a valid concern, but at the same time, in the canonical examples of tab widgets, the unselected tab's aria-controls points to nothing altogether (as it points to a tabpanel that's hidden with .is-hidden and thus not accessible to SRs). Changing from alternative 2 to alternative 1 seemed like it would address this concern, but I wasn't sure if it would be problematic from a SR point-of-view to be dynamically adding/removing aria-controls attributes. Any thoughts would be greatly appreciated - thanks!

JAWS-test commented 2 years ago

I think it is a bug that aria-controls points to a container that is not visible (display:none). I would use aria-controls only on the active tab. Besides, aria-controls has little relevance (see https://github.com/w3c/aria/issues/995).

zelliott commented 2 years ago

So then you would consider Alternative 1 to be the "most correct" out of the structures above?

I think it is a bug that aria-controls points to a container that is not visible (display:none).

If the other APG maintainers agree, then we should update the tabs example accordingly.

JAWS-test commented 2 years ago

If there would be a screen reader support of aria-controls, the first variant is much better. With the second one I would refer from the not active tab to the wrong tabpanel.