Skip to content

feat(a11y): implement ARIA tablist pattern across all tab strips (#1031)#1045

Merged
accius merged 1 commit into
accius:Stagingfrom
ceotjoe:fix/aria-tablist-pattern
Jun 1, 2026
Merged

feat(a11y): implement ARIA tablist pattern across all tab strips (#1031)#1045
accius merged 1 commit into
accius:Stagingfrom
ceotjoe:fix/aria-tablist-pattern

Conversation

@ceotjoe
Copy link
Copy Markdown
Collaborator

@ceotjoe ceotjoe commented May 31, 2026

Summary

  • Adds shared src/utils/ariaTabKeyDown.js utility for W3C APG arrow-key / Home / End navigation
  • Converts all 7 tab-strip components to the full ARIA tablist pattern: role="tablist", role="tab" + aria-selected + aria-controls, roving tabIndex, role="tabpanel" + aria-labelledby
  • Removes aria-pressed from MeshComPanel (wrong pattern for tabs)

Components updated: SettingsPanel (9 tabs), MeshComPanel, PotaSotaPanel, DXFilterManager, ActivateFilterManager, PSKFilterManager, PSKReporterPanel

Test plan

  • Open Settings panel — screen reader announces "Settings tabs" tablist, each tab announces as selected/not selected, arrow keys move between tabs
  • MeshCom panel — same pattern, no more aria-pressed
  • POTA/SOTA panel — arrow keys navigate between POTA/WWFF/SOTA/WWBOTA
  • DX Filter, Activate Filter, PSK Filter modals — tablist navigation works
  • PSKReporter tx/rx sub-tabs — tablist with arrow keys
  • Visual styling unchanged across all components

Closes #1031

🤖 Generated with Claude Code

…ius#1031)

Converts all seven tab-strip components from plain buttons to the W3C APG
Tabs pattern: role="tablist" on the container, role="tab" with aria-selected
and aria-controls on each button, role="tabpanel" with aria-labelledby on
each panel, roving tabIndex, and arrow-key / Home / End navigation via a
shared ariaTabKeyDown utility.

Components updated: SettingsPanel, MeshComPanel (removes aria-pressed),
PotaSotaPanel, DXFilterManager, ActivateFilterManager, PSKFilterManager,
PSKReporterPanel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@accius accius left a comment

Choose a reason for hiding this comment

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

Nice cleanup of the tablist pattern across all seven tab strips. The shared ariaTabKeyDown helper is exactly the kind of utility that should exist for this — arrow / Home / End wrapped behavior in one place, components just pass their tab-id array and the active state setter.

A few things I liked:

The roving tabIndex (0 on active, -1 on others) is the W3C APG pattern and means tab key skips the inactive tabs entirely — keyboard users don't have to step through every tab to leave the strip. The ref callback that populates tabRefs.current and the setActiveTab + .focus() move on arrow press completes the loop.

Dropping aria-pressed from MeshComPanel is correct — aria-pressed is the toggle-button pattern and was lying about what those tabs were. Replacing it with aria-selected + role="tab" is the right semantic.

MeshComPanel switching from conditional mount to hidden/display:none panels is the only non-trivial behavior change. It means all three tab subtrees stay mounted, which has a small memory cost but matches APG and gives screen readers stable aria-controls targets. I think that's the right tradeoff here — those panels are cheap.

PSKReporter only wires the tablist for the PSK panel mode (not for the wsjt/Decodes/QSOs mode) — that's a deliberate scope decision and reasonable. Worth a followup later but not blocking.

Good to merge.

K0CJH

@accius accius merged commit 58c9561 into accius:Staging Jun 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Accessibility] SettingsPanel + MeshComPanel tab strips should use the ARIA tablist pattern

2 participants