Skip to content

feat(a11y): add ARIA labels, keyboard nav, and screen reader support#57

Merged
dbfx merged 3 commits intomainfrom
feat/accessibility-pass
Mar 23, 2026
Merged

feat(a11y): add ARIA labels, keyboard nav, and screen reader support#57
dbfx merged 3 commits intomainfrom
feat/accessibility-pass

Conversation

@dbfx
Copy link
Contributor

@dbfx dbfx commented Mar 23, 2026

Summary

  • Foundation: focus-visible ring, skip-nav link, sr-only utility, prefers-reduced-motion media query, @axe-core/react dev integration
  • Landmarks: <main id="main-content">, <nav aria-label>, nav group role="group" with aria-labelledby, dynamic document.title on route change (localized via i18n)
  • Shared components: ConfirmDialog (role="dialog", focus trap, Escape dismiss), ScanProgress (role="progressbar", aria-live), HealthScore (role="img"), ErrorAlert (role="alert"), StatCard (role="group"), EmptyState (hidden decorative icon)
  • Sidebar: aria-current="page", aria-expanded/aria-haspopup for flyout triggers, role="menu"/role="menuitem" on flyouts, full keyboard nav (Arrow/Home/End/Escape), auto-focus on open
  • Window controls: role="toolbar", aria-label on minimize/maximize/close buttons
  • AdminBanner: role="status", labeled dismiss button

Test plan

  • npm test — 767/767 tests pass, no regressions
  • Keyboard walkthrough: Tab through the app, verify focus ring on every interactive element
  • Confirm dialog: open, verify Tab cycles within dialog, Escape closes it
  • Sidebar flyout: open submenu, Arrow Up/Down moves between items, Escape closes and returns focus
  • Screen reader: VoiceOver announces landmarks, page title changes, dialog purpose, progress values
  • npm run dev + open DevTools console: verify axe-core reports are present

🤖 Generated with Claude Code

First accessibility pass covering foundation, landmarks, shared
components, and sidebar navigation. Adds focus-visible rings, skip-nav
link, dialog focus trap, progressbar roles, landmark regions, flyout
menu keyboard navigation (Arrow/Home/End/Escape), and axe-core dev
integration. All changes are additive ARIA attributes with no logic
regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59cdf3811a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Move skip-nav link from index.html into AppShell with a JS click handler
so it doesn't rewrite the hash fragment and break routing. Restore focus
to the triggering element when ConfirmDialog closes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6350d352b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…rent nav items

Give each nested route its own distinct window title instead of the
parent section name, so screen readers and the OS task switcher show the
actual page. Only apply aria-current="page" to leaf nav items, not to
flyout parent triggers that merely contain the active child.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dbfx dbfx merged commit 56e68fc into main Mar 23, 2026
9 checks passed
@dbfx dbfx deleted the feat/accessibility-pass branch March 23, 2026 14:26
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cd3753ebe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +439 to +443
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
menuItems[(currentIndex + 1) % menuItems.length].focus()
break

Choose a reason for hiding this comment

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

P2 Badge Close the flyout menu when Tab leaves it

When a submenu is open, focus is moved into the popup, but handleKeyDown only handles arrows/Home/End/Escape. Because the items are still tabbable <button role="menuitem"> elements and there is no blur handler, pressing Tab or Shift+Tab moves focus elsewhere while the flyout stays mounted and the trigger keeps aria-expanded=true, leaving keyboard users with an orphaned popup on screen.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant