Skip to content

feat(a11y): add guardrails, tests, and accessibility infrastructure#115

Merged
sinduri-g merged 10 commits into
mainfrom
feat/a11y-tests-guardrails
Jun 6, 2026
Merged

feat(a11y): add guardrails, tests, and accessibility infrastructure#115
sinduri-g merged 10 commits into
mainfrom
feat/a11y-tests-guardrails

Conversation

@sinduri-g
Copy link
Copy Markdown
Contributor

@sinduri-g sinduri-g commented Jun 6, 2026

  • Add eslint-plugin-jsx-a11y to catch ARIA violations at lint time
  • Add tabIndex={-1} to all <main id="main-content"> elements so the skip nav link correctly moves keyboard focus (not just scrolls)
  • Add weekly scheduled a11y scan workflow and skip-nav e2e test
  • Expand ACCESSIBILITY.md with six contributor personas, screen reader quick reference, SVG and Tooltip ARIA guidelines
  • Add device/OS and WCAG criterion fields to the accessibility issue template
  • Rework ChallengeFilters disclosure from role="menu" to role="group" with aria-pressed toggle buttons; use useId() for stable ARIA IDs
  • Add loaded field to useDiscussionPosts so consumers can distinguish loading from genuinely empty; settle immediately when IDs are missing
  • Replace aria-live wrapper in DiscussionSection with a focused sr-only status node that announces a concise count, not card content
  • Fix CommunitySidebar to gate the empty-state paragraph on loaded, removing the premature flash before discussion data arrives
  • Fix scroll hint on verification <pre> to use aria-label directly instead of an unassociated sr-only span that screen readers skipped
  • Add aria-current="page" comment to NavLink so future maintainers do not replace it with a plain <a> and silently break navigation AT
  • Add reduced-motion axe scan suite; fix flaky waitForTimeout to use waitForLoadState; add HTTP 200 guard before each axe scan
  • Add regression tests covering all new hook and component behaviour

Type of change

  • feat new feature
  • fix bug fix
  • refactor no behavior change
  • docs / chore / config / perf / style / security

Manual checks

  • Screen reader tested (UI changes only)
  • New routes added to sitemap.xml, prerender array, README (routes only)

sinduri-g added 4 commits June 6, 2026 17:03
- Add `eslint-plugin-jsx-a11y` to catch ARIA violations at lint time
- Add `tabIndex={-1}` to all `<main id="main-content">` elements so the
  skip nav link correctly moves keyboard focus (not just scrolls)
- Add weekly scheduled a11y scan workflow and skip-nav e2e test
- Expand ACCESSIBILITY.md with six contributor personas, screen reader
  quick reference, SVG and Tooltip ARIA guidelines
- Add device/OS and WCAG criterion fields to the accessibility issue template
- Rework `ChallengeFilters` disclosure from `role="menu"` to `role="group"`
  with `aria-pressed` toggle buttons; use `useId()` for stable ARIA IDs
- Add `loaded` field to `useDiscussionPosts` so consumers can distinguish
  loading from genuinely empty; settle immediately when IDs are missing
- Replace `aria-live` wrapper in `DiscussionSection` with a focused sr-only
  status node that announces a concise count, not card content
- Fix `CommunitySidebar` to gate the empty-state paragraph on `loaded`,
  removing the premature flash before discussion data arrives
- Fix scroll hint on verification `<pre>` to use `aria-label` directly
  instead of an unassociated sr-only span that screen readers skipped
- Add `aria-current="page"` comment to `NavLink` so future maintainers
  do not replace it with a plain `<a>` and silently break navigation AT
- Add reduced-motion axe scan suite; fix flaky `waitForTimeout` to use
  `waitForLoadState`; add HTTP 200 guard before each axe scan
- Add regression tests covering all new hook and component behaviour

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
eslint-plugin-jsx-a11y@6.10.2 declares peer eslint up to ^9; the project
uses ESLint 10. The plugin works at runtime but npm ci fails on the peer
dep mismatch. legacy-peer-deps restores the pre-npm-7 resolution behaviour
so CI can install without --force or a version downgrade.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
…work

Adds five skills to .claude/skills/ for contributors using Claude Code.
Referenced in CLAUDE.md under the Project Skills table.

- a11y-audit: Red Team / Blue Team severity-weighted a11y audit pipeline
- keyboard: reviewing interactive elements (buttons, modals, dropdowns)
- navigation: nav components, skip links, breadcrumbs, mobile menus
- progressive-enhancement: ensures core content works without JS
- user-personalization: theme toggle, consent state, preference persistence

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Wrap nav links in `<ul role="list">` with `<li>` items so screen readers
  announce list count; `display: contents` on both elements preserves the
  existing flex layout without any prop changes
- Always render the mobile menu drawer in the DOM and hide it with the HTML
  `hidden` attribute so `aria-controls="mobile-menu"` always has a valid
  target (ARIA spec requires the referenced element to exist)
- Change the mobile drawer from `<div>` to `<nav aria-label="Mobile navigation">`
  so it is exposed as a landmark and reachable via screen reader landmark
  navigation
- Update navbar tests to assert on the `hidden` attribute instead of
  checking for `toBeNull()`, matching the new always-mounted pattern

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 6, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-06 17:01 UTC

sinduri-g added 6 commits June 6, 2026 17:41
- ChallengeFilters: render dropdown panels always in DOM (hidden attr)
  so aria-controls always resolves to a valid IDREF; switch from
  role="menu" to role="group" + aria-pressed; add arrow-key navigation
- Navbar: change mobile drawer from <nav> to <div> to avoid nested nav
  landmarks; panel is always in DOM so aria-controls is never dangling
- DiscussionSection: gate content render on loaded flag to prevent flash
  of empty state before data arrives
- useDiscussionPosts: clear stale posts/solvers/totalReplies when IDs
  transition to an empty/unmatched value
- ChallengeDetail: add targeted eslint-disable for tabIndex on <pre>
  with WCAG SC 2.1.1 rationale; remove blanket global suppression
- Tests: add regression tests for stale-state clearing, nested-nav
  guard, and loaded-gating; update selectors to :not([hidden])
- styleguide.md: update ChallengeFilters ARIA pattern and
  DiscussionSection live-region docs to match current implementation

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Use `ReactKeyboardEvent<HTMLButtonElement>` instead of a structural
  duck type for the `navigatePanel` handler in `ChallengeFilters`
- Add five tests covering ArrowDown, ArrowUp, wrap-at-last,
  wrap-at-first, and non-arrow key pass-through for the mobile
  difficulty dropdown

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- ChallengeFilters: return focus to trigger button after closing mobile
  dropdown (keyboard users lost focus to body on filter select)
- AdventureCard: add aria-label with adventure title so screen readers
  announce a concise link name instead of full card text in links list
- Navbar: silence dark logo alt (link aria-label is the accessible name)
  and update label to "offon.dev home" for explicit link purpose

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Wrap all localStorage reads/writes in try/catch (private browsing safety)
- Detect prefers-color-scheme: light on first load when no stored preference
- Update inline theme script to match useTheme initialisation logic
- Add ThemeAnnouncer in Layout.tsx — polite live region for theme changes
- Return focus to trigger button on Escape in ChallengeFilters dropdowns
- Add focus-visible ring to ChallengeFilters dropdown items and StarterNudge dismiss button
- Hide fireflies entirely under prefers-reduced-motion: reduce (display: none)
- Expand ACCESSIBILITY.md: keyboard key table, modal/dropdown patterns, roving tabindex, voice control label-in-name, WCAG criterion table
- Update Accessibility.tsx automated-testing section: add eslint-plugin-jsx-a11y entry, note weekly scan and reduced-motion run
- Sync CLAUDE.md and PERFORMANCE.md with code and process changes

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
- Add focusable="false" to brand SVGs in Footer, ChallengeShareLinks, MarkdownContent, and Contribute — prevents IE/old Edge phantom tab stops
- Fix EXT_LINK_SVG template in generate-adventures.mjs and regenerate all generated files so adventure HTML inherits the fix
- Correct four styleguide.md entries: h3 font-weight note, icon map ChevronRight vs ArrowLeft, useDiscussionPosts loaded field and reset note, .section-label class purpose, useTheme prefers-color-scheme detection, firefly reduced-motion hiding
- Correct README.md: /challenges page description
- Fix useDiscussionPosts test: wait on posts.length === 0 rather than loaded (which was already true before the new effect settled)
- Pin Playwright colorScheme to dark so theme-toggle tests are stable regardless of OS setting

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
… test

- Solvers and totalReplies were asserted synchronously after waitFor,
  relying on the implementation updating all four fields in the same
  microtask. Moving them inside waitFor makes the test resilient to
  future batching changes.

Signed-off-by: Sinduri Guntupalli <sinduri.guntupalli@dynatrace.com>
@sinduri-g sinduri-g merged commit bef6224 into main Jun 6, 2026
5 checks passed
@sinduri-g sinduri-g deleted the feat/a11y-tests-guardrails branch June 6, 2026 17:00
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.

1 participant