fix(ui): Tabs is an ARIA tab list with roving focus and a wired panel

The rail was a row of aria-pressed toggles: every tab a Tab stop, no arrows,
nothing tying a tab to its content. tablist/tab/aria-selected, one Tab stop,
arrows on either axis, Home/End, and useTabPanel for aria-controls/labelledby.
This commit is contained in:
2026-09-27 14:22:37 +02:00
parent a975ab627a
commit 922d589b09
14 changed files with 311 additions and 139 deletions
+21
View File
@@ -0,0 +1,21 @@
import { useId } from "react";
// The id half of ui/Tabs (its own file so Tabs.tsx exports only components — Fast Refresh).
export const tabId = (base: string, id: string) => `${base}tab-${id}`;
export const panelId = (base: string) => `${base}panel`;
/** The ids that tie a `Tabs` to the panel it switches: pass `idBase` to the `Tabs` and spread
* `panelProps(active)` on the element that shows the selected tab's content. Takes no argument, so
* it can sit above a page's loading / error returns while the active tab is only known below. */
export function useTabPanel() {
const base = useId();
return {
idBase: base,
panelProps: (active: string) => ({
id: panelId(base),
role: "tabpanel" as const,
"aria-labelledby": tabId(base, active),
}),
};
}