About This HTML Issue
An element with role="tab" must sit inside an element with role="tablist", or be claimed by one through the aria-owns attribute. When a tab lives outside a tablist, screen readers can't tell how many tabs are in the group or which one is selected, so the whole tab interface breaks for people who rely on assistive technology. To fix it, place your role="tab" elements inside a role="tablist" container.
The tab role only carries meaning inside a tablist. A tab controls the visibility of a matching role="tabpanel", and the tablist groups the tabs together so assistive technologies can announce position (for example, "tab 2 of 4") and move focus between tabs with the arrow keys. Take the tab out of its tablist and that grouping disappears, leaving the screen reader with a button it can't place in any structure.
The W3C validator emits this message in two slightly different wordings depending on its version: an element with the “role” value “tablist” and an element with “role=tablist”. Both report the same problem, and the fix is identical.
There are two ways to establish the required relationship. Nesting the tabs directly inside the tablist is the common one. When your layout or framework makes that nesting impossible, point aria-owns from the tablist at the tab ids instead.
Invalid example
Here the role="tab" buttons are siblings of the tablist rather than children of it, so no tablist contains or owns them.
<div class="tabs">
<div role="tablist" aria-label="Account settings"></div>
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Billing
</button>
</div>
Valid example
Moving the tabs inside the role="tablist" element gives them the parent they need.
<div class="tabs">
<div role="tablist" aria-label="Account settings">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Billing
</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1">
<p>Profile settings</p>
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>
<p>Billing settings</p>
</div>
</div>
If you genuinely can't nest the tabs in the DOM, keep them where they are and add aria-owns="tab-1 tab-2" to the role="tablist" element so assistive technologies still treat the list as the owner of those tabs.
Find issues like this automatically
Rocket Validator scans thousands of pages in seconds, detecting HTML issues across your entire site.