Skip to content

Commit 33a4323

Browse files
Kunall7890Kunal jaiswal
andauthored
a11y: implement WAI-ARIA Tabs pattern on tab navigation (#65)
Closes #28 — WAI-ARIA Tabs pattern implementation by @Kunall7890. - role=tablist + aria-label on nav - role=tab with aria-selected, aria-controls, roving tabIndex on buttons - role=tabpanel with aria-labelledby, aria-live=polite on main - ArrowRight/Left/Home/End keyboard navigation per WAI-ARIA spec - aria-label on badge counts for screen readers Co-authored-by: Kunal jaiswal <kunalljaiswal2005@gmail.com>
1 parent 6267d8b commit 33a4323

1 file changed

Lines changed: 39 additions & 4 deletions

File tree

src/panel/Panel.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,28 @@ export function Panel() {
150150
}
151151
}, [tabId, state.reduxDetected]);
152152

153+
// WAI-ARIA Tabs — arrow-key roving focus between tabs
154+
const handleTabKeyDown = useCallback((e: React.KeyboardEvent, currentIndex: number) => {
155+
const tabCount = TABS.length;
156+
let nextIndex: number | null = null;
157+
if (e.key === 'ArrowRight') {
158+
nextIndex = (currentIndex + 1) % tabCount;
159+
} else if (e.key === 'ArrowLeft') {
160+
nextIndex = (currentIndex - 1 + tabCount) % tabCount;
161+
} else if (e.key === 'Home') {
162+
nextIndex = 0;
163+
} else if (e.key === 'End') {
164+
nextIndex = tabCount - 1;
165+
}
166+
if (nextIndex !== null) {
167+
e.preventDefault();
168+
const nextTab = TABS[nextIndex];
169+
handleTabChange(nextTab.id);
170+
const nextBtn = document.getElementById(`tab-${nextTab.id}`);
171+
nextBtn?.focus();
172+
}
173+
}, [handleTabChange]);
174+
153175
useEffect(() => {
154176
fetchState();
155177

@@ -427,25 +449,38 @@ export function Panel() {
427449
</div>
428450
</header>
429451

430-
<nav className="tab-nav">
431-
{TABS.map(tab => {
452+
<nav className="tab-nav" role="tablist" aria-label="React Debugger sections">
453+
{TABS.map((tab, index) => {
432454
const badge = getBadge(tab.id);
433455
return (
434456
<button
435457
key={tab.id}
458+
id={`tab-${tab.id}`}
459+
role="tab"
460+
aria-selected={activeTab === tab.id}
461+
aria-controls={`panel-${tab.id}`}
462+
tabIndex={activeTab === tab.id ? 0 : -1}
436463
className={`tab-button ${activeTab === tab.id ? 'active' : ''}`}
437464
onClick={() => handleTabChange(tab.id)}
465+
onKeyDown={(e) => handleTabKeyDown(e, index)}
438466
>
439467
<span className="tab-label">{tab.label}</span>
440468
{badge !== undefined && badge > 0 && (
441-
<span className="tab-badge">{badge}</span>
469+
<span className="tab-badge" aria-label={`${badge} items`}>{badge}</span>
442470
)}
443471
</button>
444472
);
445473
})}
446474
</nav>
447475

448-
<main className="tab-content">
476+
<main
477+
className="tab-content"
478+
role="tabpanel"
479+
id={`panel-${activeTab}`}
480+
aria-labelledby={`tab-${activeTab}`}
481+
aria-live="polite"
482+
tabIndex={0}
483+
>
449484
{!isDebuggerEnabled && state.timelineEvents.length === 0 ? (
450485
<div className="debugger-disabled-placeholder">
451486
<div className="placeholder-icon">

0 commit comments

Comments
 (0)