| @astryxdesign/core | minor |
|---|
[breaking] TabList: the strip now speaks the WAI-ARIA tabs pattern — role="tablist" on the strip, role="tab" and aria-selected on the tabs, and aria-controls pointing at the panel each tab opens, from a new panelId prop on Tab. There is no new prop for it: TabList declares role?: AriaRole and reads it. role="tablist" asks for the pattern, any other role passes through to the element untouched, and left unset the strip picks for itself — the tabs pattern when it holds nothing but tabs, the navigation landmark when it holds anything else. The keyboard behaviour the pattern asks for was already there: arrows move between tabs, Tab leaves the strip.
This changes the default. A strip of plain tabs is a <nav> landmark today and becomes a tablist: the selected tab is announced as a selected tab rather than the current item, aria-current gives way to aria-selected, and — because a tablist reports itself as horizontal — ArrowUp and ArrowDown stop moving between tabs and scroll the page instead. ArrowLeft, ArrowRight, Home, End, Tab and the roving tab stop are unchanged, as is everything the strip looks like. A strip that really is page navigation keeps the landmark by saying so with role="navigation".
Two things the strip will not do to you on its own. A tab with an href navigates, so a strip holding one stays navigation — an href is only ignored where role="tablist" was asked for explicitly, and then a development warning says so. And a tab that controls nothing is only worth mentioning to a consumer who asked for the pattern, so that warning too is limited to the explicit role; either panelId or an aria-controls you wrote yourself satisfies it, and a hand-written one is never overwritten. aria-controls is emitted only when you supply the id: pointing at a panel that does not exist is an invalid attribute value, which is worse than saying nothing.
The pick is made from the rendered DOM rather than from children, so a menu behind a conditional, inside a .map, or wrapped in a component of your own still counts, and it follows the strip if its contents change later — without remounting the tabs.
@cixzhang