Skip to content

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

@hoainho

Description

@hoainho

🌟 Before claiming this issue

Two quick steps before you open a PR:

  1. ⭐ Star the repository β€” low-friction signal that you'll follow through
  2. πŸ’¬ Comment I'll take this (or similar) below β€” prevents two contributors racing on the same issue

Full policy: CONTRIBUTING.md β†’ How to claim. If a claim is older than 7 days with no PR, you can reclaim it politely.


What

src/panel/Panel.tsx lines 425-441 render the main tab navigation as 8 <button> elements inside a <nav> element. They lack the WAI-ARIA Tabs pattern attributes, so screen readers announce them as 8 unnamed buttons with no relationship to their panel content.

Why it matters

  • Screen reader users can't tell which tab is selected, which panel they're in, or how many tabs exist.
  • The <main className="tab-content"> (line 443) updates when polls bring new data, but has no aria-live, so screen reader users get zero announcement that 50+ Timeline events just appeared.
  • This is one of the most common a11y patterns; getting it right is a strong signal that the project cares about accessibility.

How to fix

<nav> becomes the tablist (line 425)

<nav className="tab-nav" role="tablist" aria-label="React Debugger sections">

Each tab <button> (line 429) gets ARIA attributes

<button
  key={tab.id}
  role="tab"
  id={`tab-${tab.id}`}
  aria-selected={activeTab === tab.id}
  aria-controls={`panel-${tab.id}`}
  tabIndex={activeTab === tab.id ? 0 : -1}
  className={`tab-button ${activeTab === tab.id ? 'active' : ''}`}
  onClick={() => handleTabChange(tab.id)}
>

<main> becomes the tabpanel (line 443)

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

Bonus β€” keyboard arrow navigation

Per the spec, arrow keys should move focus between tabs. Add a handleTabKeyDown callback (full code in WAI-ARIA Tabs example).

Verify

  1. Install axe DevTools.
  2. Open the panel, run axe β€” note "Tabs" rule violations.
  3. Apply fix.
  4. Re-run axe β€” confirm zero "Tabs" violations.
  5. Test with VoiceOver (Mac) or NVDA (Windows): focus a tab, confirm the announcement includes "tab, selected, X of 8".

Acceptance criteria

  • <nav> has role="tablist" and aria-label
  • Each tab button has role="tab", aria-selected, aria-controls, id, tabIndex (roving)
  • <main> has role="tabpanel", id, aria-labelledby, aria-live
  • axe DevTools shows zero Tabs violations
  • PR description includes axe before/after + manual screen-reader test note

Scope: S (~15 lines JSX) | Effort: ~1h including screen-reader testing | Risk: Lane Tiny | WCAG: 4.1.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    a11yAccessibility improvementsenhancementNew feature or requestgood first issueGood for newcomers

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions