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 <span> 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 17:32:18 +02:00
parent ff513e1712
commit 914527edc6
2 changed files with 11 additions and 3 deletions
+5 -3
View File
@@ -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<LabelInput[]>([]);
@@ -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 }) => <span aria-selected={isActive}>{t(`authorities.${k}`)}</span>}
{t(`authorities.${k}`)}
</NavLink>
))}
</div>
@@ -78,7 +80,7 @@ export function AuthoritiesPage() {
<form onSubmit={onCreate} className="space-y-2 border-t pt-3">
<div className="text-sm font-medium">
{t("authorities.new")} · {t(`authorities.${kind}`)}
{t("authorities.new")} · {t(`authorities.${currentKind}`)}
</div>
<LabelEditor value={labels} onChange={setLabels} />