From 914527edc626c27b2fbd2681b4793a6c8319d567 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Thu, 4 Jun 2026 17:32:18 +0200 Subject: [PATCH] fix(web): place aria-selected on the role=tab element (#32) + assert it Per WAI-ARIA, aria-selected must be on the same element carrying role="tab". The previous impl placed it on an inner via the render-prop, making it invisible to assistive technology. Move both role="tab" and aria-selected to the NavLink, compute currentKind once from useParams, drop the render-prop. Add a Vitest assertion that the selected tab has aria-selected="true" and an unselected tab has aria-selected="false". Co-Authored-By: Claude Sonnet 4.6 --- web/src/authorities/authorities-page.tsx | 8 +++++--- web/src/authorities/authorities.test.tsx | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/web/src/authorities/authorities-page.tsx b/web/src/authorities/authorities-page.tsx index c43e320..13aa20d 100644 --- a/web/src/authorities/authorities-page.tsx +++ b/web/src/authorities/authorities-page.tsx @@ -18,8 +18,9 @@ export function AuthoritiesPage() { const lang = i18n.language.startsWith("sv") ? "sv" : "en"; const isValidKind = (KINDS as readonly string[]).includes(kind ?? ""); + const currentKind = isValidKind ? (kind as string) : "person"; - const { data: authorities, isLoading, isError } = useAuthorities(isValidKind ? (kind as string) : "person"); + const { data: authorities, isLoading, isError } = useAuthorities(currentKind); const create = useCreateAuthority(); const [labels, setLabels] = useState([]); @@ -50,11 +51,12 @@ export function AuthoritiesPage() { key={k} to={`/authorities/${k}`} role="tab" + aria-selected={k === currentKind} className={({ isActive }) => `rounded px-3 py-1 text-sm ${isActive ? "bg-neutral-800 text-white" : "border"}` } > - {({ isActive }) => {t(`authorities.${k}`)}} + {t(`authorities.${k}`)} ))} @@ -78,7 +80,7 @@ export function AuthoritiesPage() {
- {t("authorities.new")} · {t(`authorities.${kind}`)} + {t("authorities.new")} · {t(`authorities.${currentKind}`)}
diff --git a/web/src/authorities/authorities.test.tsx b/web/src/authorities/authorities.test.tsx index 02a8ff8..67406a7 100644 --- a/web/src/authorities/authorities.test.tsx +++ b/web/src/authorities/authorities.test.tsx @@ -36,6 +36,12 @@ test("kind tabs link to the other kinds", async () => { expect(await screen.findByRole("tab", { name: /place/i })).toHaveAttribute("href", "/authorities/place"); }); +test("aria-selected is on the tab element and reflects the active kind", async () => { + renderApp(tree(), { route: "/authorities/person" }); + expect(await screen.findByRole("tab", { name: /^person$/i })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByRole("tab", { name: /^place$/i })).toHaveAttribute("aria-selected", "false"); +}); + test("create without EN label shows required alert and does not POST", async () => { let posted = false; server.use(