feat(frontend): achieve WCAG 2.1 AA accessibility (resolves #70) - #96
Closed
Moonwalker-rgb wants to merge 2 commits into
Closed
feat(frontend): achieve WCAG 2.1 AA accessibility (resolves #70)#96Moonwalker-rgb wants to merge 2 commits into
Moonwalker-rgb wants to merge 2 commits into
Conversation
- Add canonical <main id="main-content"> skip-link SiteHeader SiteFooter and client-side RouteAnnouncer in app/layout.tsx. - Replace nested <main> in app/admin/layout.tsx with labelled <section>. - Add aria-label to AdminSidebar nav and AdminHeader banner. - Rewrite AccessibilityDashboard to drive a real axe-core audit (lazy-imported WCAG 2.1 A & AA tags) with graceful fallback and a narrow catch on axe-typed errors. - Compose alert prefix plus detail for unrelated failure diagnostics. - Promote axe-core from devDependencies to dependencies (runtime import). - Scope .high-contrast CSS to inside main / [role="main"]; add a .reduce-motion companion rule alongside prefers-reduced-motion; strengthen focus ring via .focus-visible-enabled. - Add documenting NOTE in pages/_app.tsx about landmark split. - Add a11y regression tests (jest + RTL). - Wire npm run test:a11y into the frontend CI job with continue-on-error. - Add eslint-plugin-jsx-a11y recommended rules to lint. Refs: Epondia#70
Contributor
|
@Moonwalker-rgb Hey! Thanks for the PR. It looks like a couple of the CI/CD checks are failing. Could you take a look at the logs, push a fix, and get them green? The contracts and security scans passed perfectly!" |
…pondia#96) Resolves GitHub Actions failure on PR Epondia#96 Install-dependencies step. Root cause: the project is an npm workspaces monorepo (contracts, backend, frontend in root package.json). PR Epondia#96 modified frontend/package.json (axe-core ^4.12.1, eslint-plugin-jsx-a11y) without also updating the root package-lock.json, so the single-source-of-truth lockfile drifts and both Frontend and Backend jobs fail at npm ci. Two changes: - frontend/package.json: removed jest-axe (PR Epondia#96 body notes it was dropped, but the dep line was left behind). - package-lock.json (root): regenerated to match; force-tracked with git add -f because root .gitignore still lists it (a one-line follow-up should drop that gitignore line). Refs: failure run 27943404202; pairs with Epondia#95 and Epondia#100.
Moonwalker-rgb
force-pushed
the
fix/issue-70-wcag-aa
branch
from
June 22, 2026 10:35
693d352 to
eb73a26
Compare
Contributor
|
@Moonwalker-rgb kindly resolve conflicts |
Contributor
|
Closing stale PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #70 ("Achieve WCAG 2.1 AA accessibility compliance") by establishing the four required App-Router landmarks on every page (
main,nav,banner,contentinfo), running a real axe-core audit from the in-app AccessibilityDashboard, and locking the new hygiene in with regression tests and CI lint rules.Definition-of-Done coverage
useFocusTrapandui/button.tsxuseFocusTrapaltwarnings addressedglobals.css+.high-contrastopt-in scoped tomainapp/layout.tsx<RouteAnnouncer>in App Router + existing announcer in_app.tsxaxe-coreruns from the dashboard with WCAG 2.1 A & AA tagsWhat changes
Added
frontend/src/components/accessibility/RouteAnnouncer.tsx— client component that announces App-Router route changes via a polite aria-live status region usingnext/navigation.usePathname, with rAF batching so repeated pathnames always trigger a DOM mutation.frontend/src/components/accessibility/SiteHeader.tsx— minimal banner landmark for the App Router.frontend/src/components/accessibility/SiteFooter.tsx— contentinfo landmark for the App Router.frontend/src/test/accessibility.test.tsx— regression tests for the route announcer and the canonical landmark shape.frontend/src/hooks/__tests__/AccessibilityDashboard.test.tsx— semantic baseline plus axe-core success / fallback / unrelated-failure paths.Modified
frontend/src/app/layout.tsx— adds the canonical<main id="main-content" tabindex="-1">, the skip-link,<SiteHeader>,<RouteAnnouncer>and<SiteFooter>so every App-Router route exposes all four DoD landmarks.frontend/src/app/admin/layout.tsx— the previously nested<main>is replaced with a labelled<section aria-label="Admin content">so the landmark-unique axe rule does not trip on every admin route.frontend/src/components/Admin/AdminSidebar.tsx—aria-label="Admin navigation"on the existing<nav>.frontend/src/components/Admin/AdminHeader.tsx—role="banner"+aria-label="Admin top bar"on the existing<header>.frontend/src/hooks/AccessibilityDashboard.tsx— rewritten to lazily importaxe-core, run the WCAG 2.1 A & AA rule tags, fall back to a narrow heuristic dataset on axe-typed load errors, and emit a stable "axe-core run failed unexpectedly" prefix plus the underlying error message for unrelated failures.frontend/src/styles/globals.css—.high-contrastis now scoped to insidemain/[role="main"]so opt-in does not repaint global dialogs or badges; a.reduce-motioncompanion goes alongsideprefers-reduced-motion; an opt-in.focus-visible-enabledstrengthens the focus ring.frontend/src/pages/_app.tsx— added a NOTE comment clarifying the landmark/announcer authority split between the App Router and the Pages Router so future contributors don't duplicate work.Dependencies
axe-core(^4.10.3) — promoted fromdevDependenciestodependenciesbecause the dashboard dynamically imports it at runtime in production.eslint-plugin-jsx-a11y(^6.10.2) — added todevDependenciesand extended in.eslintrc.jsonso future regressions are caught at lint time.CI
Accessibility regression tests (issue #70)step in.github/workflows/ci.ymlrunsnpm run test:a11y(jest, scoped to the new a11y suites) withcontinue-on-error: trueso existing flakiness does not block unrelated work. The step also publishes a job-summary snippet pointing operators at the suite.Why these specific decisions
<main>— the obvious "wrap each segment in its own<main>" pattern violates the HTML spec and trips landmark-unique. The App Router root owns one<main id="main-content">and admin routes get a labelled<section>instead.axe-coreas runtime dep — loaded on demand by the audit dashboard; lazy chunk separation keeps the cost off unrelated users.main— a global high-contrast theme repaints badges, dialogs and intentionally-coloured chips. Scoping to inside the page surface keeps opt-in safe.ChunkLoadError,ERR_MODULE_NOT_FOUND, or messages containingaxe-core/axe.run. Unrelated failures are surfaced viastate.errorandconsole.errorso they get fixed instead of being silently masked.Validation
npx tsc --noEmit— zero new errors introduced by this PR. (Pre-existing errors inuseCollaborationSession,bciService,mlModel,performance-monitor,performance-optimization,stellar,pages/analytics.tsxare untouched and out of scope.)npx jest src/test/accessibility.test.tsx src/hooks/__tests__/AccessibilityDashboard.test.tsx --runInBand— 16/16 green.npx jest-axewas considered and dropped from the test suites becausejest-axe@9invokesaxe-core.getRules()which the runtime build does not expose and because the package no longer ships compatible TypeScript declarations. The dashboard's static baseline is instead covered by a hand-rolled semantic smoke check (role="region"+ accessible name +aria-busyflipping during scans).Out of scope (follow-up issues recommended)
lighthouse-cior@axe-core/playwrightstep so the DoD's "0 critical/serious violations" claim is verified on every PR.outline-offsetwhich is direction-agnostic, but it has not been visually verified underdir="rtl".