diff --git a/src/components/ui/Tabs.test.tsx b/src/components/ui/Tabs.test.tsx index aca1135..b90a92e 100644 --- a/src/components/ui/Tabs.test.tsx +++ b/src/components/ui/Tabs.test.tsx @@ -81,6 +81,37 @@ describe("Tabs — count badge", () => { }); }); +describe("Tabs — aria-controls only on the active tab (#710)", () => { + it("sets aria-controls on the active tab", () => { + render( {}} />); + expect(screen.getByRole("tab", { name: "Active" })).toHaveAttribute( + "aria-controls", + "tabpanel-active", + ); + }); + + it("omits aria-controls on inactive tabs, since their tabpanel isn't in the DOM", () => { + render( {}} />); + expect(screen.getByRole("tab", { name: "Completed" })).not.toHaveAttribute( + "aria-controls", + ); + expect(screen.getByRole("tab", { name: "Archived" })).not.toHaveAttribute( + "aria-controls", + ); + }); + + it("moves aria-controls to whichever tab becomes active", () => { + render( {}} />); + expect(screen.getByRole("tab", { name: "Completed" })).toHaveAttribute( + "aria-controls", + "tabpanel-completed", + ); + expect(screen.getByRole("tab", { name: "Active" })).not.toHaveAttribute( + "aria-controls", + ); + }); +}); + describe("Tabs — keyboard navigation (#24)", () => { const threeTabs = [ { key: "first", label: "First" }, diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx index a5236b0..ac0a972 100644 --- a/src/components/ui/Tabs.tsx +++ b/src/components/ui/Tabs.tsx @@ -79,7 +79,13 @@ export function Tabs({ role="tab" id={`tab-${tab.key}`} aria-selected={isActive} - aria-controls={`tabpanel-${tab.key}`} + // Only the active tab's panel actually exists in the DOM (a + // consumer like ContributorDashboardClient renders just the one + // tabpanel matching the current tab). aria-controls on an + // inactive tab pointed at a missing id -- an ARIA reference + // error flagged by axe and confusing for assistive tech + // following the relationship (#710). + aria-controls={isActive ? `tabpanel-${tab.key}` : undefined} tabIndex={isActive ? 0 : -1} onClick={() => onChange(tab.key)} onKeyDown={(e) => handleKeyDown(e, tab.key)}