Skip to content

a11y: implement WAI-ARIA Tabs pattern on tab navigation#65

Merged
hoainho merged 1 commit into
hoainho:mainfrom
Kunall7890:a11y/wai-aria-tabs-pattern
Jun 8, 2026
Merged

a11y: implement WAI-ARIA Tabs pattern on tab navigation#65
hoainho merged 1 commit into
hoainho:mainfrom
Kunall7890:a11y/wai-aria-tabs-pattern

Conversation

@Kunall7890

Copy link
Copy Markdown
Contributor

Linked issue

Closes #28

Type of change

  • Bug fix (non-breaking change that fixes an issue)

What changed

Implemented the WAI-ARIA Tabs pattern on the main tab navigation in
Panel.tsx. Only one file changed.

  • gets role="tablist" and aria-label="React Debugger sections"
  • Each tab gets role="tab", id, aria-selected, aria-controls,
    and roving tabIndex (0 for active, -1 for inactive)
  • gets role="tabpanel", id, aria-labelledby, aria-live="polite", and tabIndex={0}
  • Added handleTabKeyDown callback: ArrowRight/ArrowLeft/Home/End move
    focus between tabs per the WAI-ARIA Tabs spec

Testing notes

$ npm run build
(succeeds with no errors)

$ grep -n "role="tablist"" src/panel/Panel.tsx
452:

$ grep -n "aria-selected" src/panel/Panel.tsx
460: aria-selected={activeTab === tab.id}

$ grep -n "role="tabpanel"" src/panel/Panel.tsx
478: role="tabpanel"

$ grep -n "aria-live" src/panel/Panel.tsx
481: aria-live="polite"

Keyboard tested:

  • Tab focuses the active tab
  • ArrowRight moves to next tab, ArrowLeft to previous
  • Home jumps to first tab, End jumps to last tab
  • Enter/Space selects the focused tab

Claim confirmation

  • I starred the repo
  • I commented I'll take this on the issue before starting work.

Checklist

  • npm run build succeeds locally
  • role="tablist", role="tab", role="tabpanel" all present
  • aria-selected, aria-controls, aria-labelledby all wired correctly
  • aria-live="polite" on tabpanel for dynamic content announcements
  • Roving tabIndex implemented (0 for active, -1 for inactive)
  • Arrow key navigation works (ArrowLeft, ArrowRight, Home, End)
  • No new runtime dependencies added
  • WCAG 4.1.2 (Name, Role, Value) satisfied

Additional notes

aria-live="polite" is set on the tabpanel so screen readers announce
new Timeline events as they arrive without interrupting the user.
Badge counts on tabs have aria-label="X items" so screen readers
announce the number meaningfully instead of just reading the digit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces WAI-ARIA compliant keyboard navigation and accessibility attributes to the tabs and tab panel in Panel.tsx. The review feedback highlights two important improvements: first, removing aria-live="polite" from the main tab panel to prevent screen readers from being overwhelmed by real-time debugger updates, and second, replacing the global document.getElementById call with a scoped query selector to ensure the component remains safe and robust when multiple instances are rendered.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/panel/Panel.tsx
Comment on lines +476 to +483
<main
className="tab-content"
role="tabpanel"
id={`panel-${activeTab}`}
aria-labelledby={`tab-${activeTab}`}
aria-live="polite"
tabIndex={0}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Applying aria-live="polite" to the entire <main> tab panel is an accessibility anti-pattern.

Because this is a debugger panel with real-time streaming data (such as timeline events, performance metrics, and memory updates), any background update will trigger continuous screen reader announcements. This will overwhelm screen reader users and make the tool extremely noisy and difficult to use.

aria-live should only be used on small, specific regions (like a status message or toast notification) rather than large content containers. Please remove aria-live="polite" from the main tab panel.

Suggested change
<main
className="tab-content"
role="tabpanel"
id={`panel-${activeTab}`}
aria-labelledby={`tab-${activeTab}`}
aria-live="polite"
tabIndex={0}
>
<main
className="tab-content"
role="tabpanel"
id={`panel-${activeTab}`}
aria-labelledby={`tab-${activeTab}`}
tabIndex={0}
>

Comment thread src/panel/Panel.tsx
Comment on lines +170 to +171
const nextBtn = document.getElementById(`tab-${nextTab.id}`);
nextBtn?.focus();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using global document.getElementById inside a React component is a code smell and can lead to bugs if multiple instances of this panel are rendered (e.g., in testing environments or multiple DevTools windows). Scoping the query to the component's subtree is safer and more idiomatic.

Since the event handler is attached directly to the tab buttons, you can leverage e.currentTarget.parentElement to query only within the parent <nav> container.

Suggested change
const nextBtn = document.getElementById(`tab-${nextTab.id}`);
nextBtn?.focus();
const nextBtn = (e.currentTarget as HTMLElement).parentElement?.querySelector<HTMLButtonElement>(`#tab-${nextTab.id}`);
nextBtn?.focus();

@hoainho hoainho left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Kunall7890! This is a clean, spec-compliant WAI-ARIA Tabs implementation.

A few observations from review:

Correct:

  • role=\tablist\ + aria-label on <nav>
  • role=\tab\ with aria-selected + aria-controls on each button ✅
  • Roving tabIndex (0/-1) ✅
  • ArrowRight/Left/Home/End key handlers ✅
  • role=\tabpanel\ with aria-labelledby on <main>
  • aria-label on badge counts ✅
  • Build passes, no new deps ✅

Minor (not blocking):

  • aria-live=\polite\ on tabpanel — in a debugging panel where content updates can be frequent, this may announce too aggressively. If users report 'chattiness', we can switch to aria-live=\off\ (inert) and rely on the focus management alone. Let's keep it for now.
  • document.getElementById vs scoped ref — safe here since there's exactly one Panel instance per DevTools window, but worth noting for future component extraction.

Great work as always! 🚀

@hoainho hoainho added the pre-star-rule PR opened before the star-check rule landed (2026-06-01); exempt from Star Check CI label Jun 8, 2026
@hoainho hoainho merged commit 33a4323 into hoainho:main Jun 8, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pre-star-rule PR opened before the star-check rule landed (2026-06-01); exempt from Star Check CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a11y: implement WAI-ARIA Tabs pattern on tab navigation

2 participants