feat(a11y): add guardrails, tests, and accessibility infrastructure#115
Merged
Conversation
- 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>
Contributor
|
- 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>
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.
eslint-plugin-jsx-a11yto catch ARIA violations at lint timetabIndex={-1}to all<main id="main-content">elements so the skip nav link correctly moves keyboard focus (not just scrolls)ChallengeFiltersdisclosure fromrole="menu"torole="group"witharia-pressedtoggle buttons; useuseId()for stable ARIA IDsloadedfield touseDiscussionPostsso consumers can distinguish loading from genuinely empty; settle immediately when IDs are missingaria-livewrapper inDiscussionSectionwith a focused sr-only status node that announces a concise count, not card contentCommunitySidebarto gate the empty-state paragraph onloaded, removing the premature flash before discussion data arrives<pre>to usearia-labeldirectly instead of an unassociated sr-only span that screen readers skippedaria-current="page"comment toNavLinkso future maintainers do not replace it with a plain<a>and silently break navigation ATwaitForTimeoutto usewaitForLoadState; add HTTP 200 guard before each axe scanType of change
featnew featurefixbug fixrefactorno behavior changedocs/chore/config/perf/style/securityManual checks