People have asked the difference between aria-controls vs aria-owns. The aria-controls is intended parent child relationships based on hierarchy and aria-owns is not.
I want to get very specific. What exactly does aria-controls do that aria-owns does not.
From the Aria Authoring Practices document:
Two relationships expand the logical structure of a WAI-ARIA Web application. These are aria-owns
and aria-controls
. The aria-owns
relationship completes the parent/child relationship when it cannot be completely determined from the DOM created from the parsing of the markup. The aria-controls
relationship defines a cause-and-effect relationship so that assistive technologies may navigate to content effected by and changes to the content where the user is operating.
So what that means in practice is that if you have a tab control with a tab panel. The tablist must own the tabs and the tabs control their respective tab panels. The tabs are expected to live hierarchically as children of the tablist and if they do not, then you must use aria-owns
to point to them.
The tab panels can live wherever you want but you should point to them from the tabs (using aria-controls
) so the assistive technology can find them and expose them to the user (with keyboard shortcuts for example)
Example of tabs from the Open Ajax Alliance tab panel example
<ul class="tablist" role="tablist">
<li id="tab1" class="tab selected" aria-controls="panel1" aria-selected="true" role="tab" tabindex="0">Crust</li>
<li id="tab2" class="tab" aria-controls="panel2" role="tab" aria-selected="false" tabindex="-1">Veggies</li>
<li id="tab3" class="tab" aria-controls="panel3" role="tab" aria-selected="false" tabindex="-1">Carnivore</li>
<li id="tab4" class="tab" aria-controls="panel4" role="tab" aria-selected="false" tabindex="-1">Delivery</li>
</ul>
<div id="panel1" class="panel" aria-labelledby="tab1" role="tabpanel" aria-hidden="false" style="display: block;">
</div>
<div id="panel2" class="panel" aria-labelledby="tab2" role="tabpanel" aria-hidden="true" style="display: none;">
</div>
<div id="panel3" class="panel" aria-labelledby="tab3" role="tabpanel" aria-hidden="true" style="display: none;">
</div>
<div id="panel4" class="panel" aria-labelledby="tab4" role="tabpanel" aria-hidden="true" style="display: none;">
</div>
That markup has the required hierarchical relationship between the tablist and the tabs as well as the semantic markup of the fact that each tab controls its respective tabpanel.
The markup could have been done like this (I have omitted the required aria-setsize that would make up for the replacement of the <ul>
for brevity)
<div class="tablist" role="tablist" aria-owns="tab1 tab2 tab3 tab4"></div>
<div id="tab1" class="tab selected" aria-controls="panel1" aria-selected="true" role="tab" tabindex="0">Crust</div>
<div id="tab2" class="tab" aria-controls="panel2" role="tab" aria-selected="false" tabindex="-1">Veggies</div>
<div id="tab3" class="tab" aria-controls="panel3" role="tab" aria-selected="false" tabindex="-1">Carnivore</div>
<div id="tab4" class="tab" aria-controls="panel4" role="tab" aria-selected="false" tabindex="-1">Delivery</div>
<div id="panel1" class="panel" aria-labelledby="tab1" role="tabpanel" aria-hidden="false" style="display: block;">
</div>
<div id="panel2" class="panel" aria-labelledby="tab2" role="tabpanel" aria-hidden="true" style="display: none;">
</div>
<div id="panel3" class="panel" aria-labelledby="tab3" role="tabpanel" aria-hidden="true" style="display: none;">
</div>
<div id="panel4" class="panel" aria-labelledby="tab4" role="tabpanel" aria-hidden="true" style="display: none;">
</div>