TabList uses the navigation-links pattern, not ARIA Tabs (no role=tab/tabpanel, aria-selected, or arrow-key navigation)
- Dominant language
- TypeScript
- Stars
- 13k
- Forks
- 1.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 690
Description
## Summary
`TabList` renders the **navigation-links** pattern (`` containing ``/``), not the WAI-ARIA **Tabs** pattern. For its documented primary use case — "switch between related views without leaving the page" — screen-reader users hear "button/link, current page" instead of "tab, selected, 2 of 5", there is no programmatic tab↔panel association, and there is no arrow-key navigation across the tab strip.
## Details
- `packages/core/src/TabList/TabList.tsx` — container is ``, no `role="tablist"`, no `aria-orientation`.
- `packages/core/src/TabList/Tab.tsx` — items are ``/anchor with `aria-current="page"`; no `role="tab"`, no `aria-selected`, no `aria-controls`, no roving tabindex, no Arrow/Home/End handling.
- No `TabPanel` component exists (no `role="tabpanel"`, no `aria-labelledby` back to the tab).
## The design tension
`value`/`onChange` are required (in-page switching → APG **Tabs**), but `href` is also supported (route navigation → **nav landmark**). These map to two different, mutually exclusive ARIA models:
- **Anchor/route tabs:** `` + `` is already the *correct* APG navigation pattern. No change needed.
- **Button/in-page tabs:** should be `role="tablist"` / `role="tab"` (`aria-selected`, roving tabindex, Arrow/Home/End) with associated `role="tabpanel"` regions. This is the case that's currently mis-patterned.
## Options
1. **Split the semantics by usage.** When a tab has no `href`, render the Tabs pattern (tablist/tab + roving tabindex + arrow keys + `aria-selected`); when `href` is present, keep the nav-links pattern. Requires introducing a `TabPanel` primitive (or an `aria-controls`/`id` convention) so consumers can associate panels. **Breaking-ish**: consumers of in-page tabs would wire panels for full compliance.
2. **Introduce a dedicated Tabs component** (`Tabs`/`TabPanel`) for the in-page case and keep `TabList` as explicit tab-styled navigation (documented as nav, not tabs). Clearest separation; no overloaded component.
3. **Keyboard-only interim improvement (non-breaking):** add roving tabindex + Arrow/Home/End focus movement to the tab strip while leaving roles as-is. Improves keyboard UX but does not resolve the role/tabpanel semantics.
## Recommendation
Option 1 or 2 (a real Tabs pattern with a `TabPanel` primitive) is required to actually close the gap; the choice affects public API and every `TabList` consumer, so it needs a design decision before implementation. Filing for triage rather than shipping a unilateral breaking change.
Reference: WAI-ARIA APG Tabs pattern. The repo's `SegmentedControl` is a good in-repo example of correct roving-tabindex + arrow-key selection.
Contributor guide
Assessment
This issue has not been assessed yet.