From da51e80601393da4654379897b2d48012fe51faf Mon Sep 17 00:00:00 2001 From: Yggdrasil Date: Wed, 14 Oct 2026 00:48:50 -0400 Subject: [PATCH] fix(a11y): only set aria-controls on the active tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the active tab's panel renders, so inactive tabs' aria-controls pointed at tabpanel ids not in the DOM — an ARIA reference error. Omit aria-controls on inactive tabs. Resolves #710 --- src/components/ui/Tabs.tsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx index a5236b0..e51c1c6 100644 --- a/src/components/ui/Tabs.tsx +++ b/src/components/ui/Tabs.tsx @@ -14,12 +14,9 @@ export function Tabs({ }) { const tabRefs = useRef>(new Map()); - const focusTab = useCallback( - (key: T) => { - tabRefs.current.get(key)?.focus(); - }, - [], - ); + const focusTab = useCallback((key: T) => { + tabRefs.current.get(key)?.focus(); + }, []); const handleKeyDown = useCallback( (e: React.KeyboardEvent, currentKey: T) => { @@ -40,10 +37,14 @@ export function Tabs({ switch (e.key) { case "ArrowRight": - nextIdx = rtl ? (idx - 1 + keys.length) % keys.length : (idx + 1) % keys.length; + nextIdx = rtl + ? (idx - 1 + keys.length) % keys.length + : (idx + 1) % keys.length; break; case "ArrowLeft": - nextIdx = rtl ? (idx + 1) % keys.length : (idx - 1 + keys.length) % keys.length; + nextIdx = rtl + ? (idx + 1) % keys.length + : (idx - 1 + keys.length) % keys.length; break; case "Home": nextIdx = 0; @@ -79,7 +80,11 @@ export function Tabs({ role="tab" id={`tab-${tab.key}`} aria-selected={isActive} - aria-controls={`tabpanel-${tab.key}`} + // Only the ACTIVE tab's panel is rendered (ContributorDashboard + // mounts one tabpanel). aria-controls on an inactive tab pointed + // at a tabpanel id that isn't in the DOM — an ARIA reference + // error. Omit it on inactive tabs (#710). + aria-controls={isActive ? `tabpanel-${tab.key}` : undefined} tabIndex={isActive ? 0 : -1} onClick={() => onChange(tab.key)} onKeyDown={(e) => handleKeyDown(e, tab.key)}