Skip to content

Commit a035701

Browse files
authored
Merge pull request #196 from scriptnovaa/feature/issue-68-accessibility
feat: add accessibility (a11y) improvements (closes #68)
2 parents 865dab4 + 01c0a40 commit a035701

7 files changed

Lines changed: 566 additions & 114 deletions

File tree

docs/ACCESSIBILITY.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Accessibility (a11y)
2+
3+
## Standards
4+
5+
WCAG 2.1 AA compliance target.
6+
7+
## What's implemented
8+
9+
### Landmarks & structure
10+
- `<header>`, `<main id="main-content">`, `<section>` with `aria-labelledby` throughout `App.jsx`
11+
- `<h1>``<h2>` heading hierarchy (screen-reader-only `<h2>` where visual heading isn't needed)
12+
13+
### Skip navigation
14+
- "Skip to main content" link at the top of the page — visible on focus, hidden otherwise (`.skip-link` in `index.css`)
15+
16+
### ARIA
17+
- All interactive elements have `aria-label` or associated `<label>`
18+
- Form inputs use `aria-invalid` + `aria-describedby` pointing to inline error messages
19+
- Buttons use `aria-busy` during async operations
20+
- Disclosure buttons use `aria-expanded` + `aria-controls`
21+
- Modals use `role="dialog"`, `aria-modal="true"`, `aria-labelledby`
22+
- Decorative icons marked `aria-hidden="true"`
23+
- Status messages use `role="alert"` / `aria-live="polite"` / `aria-live="assertive"` as appropriate
24+
- Global loading state announced via a hidden `aria-live="polite"` region
25+
26+
### Focus management
27+
- `useFocusTrap` hook (`src/hooks/useFocusTrap.js`) traps focus inside modals (QRCodeModal, TxModal)
28+
- Focus returns to the triggering element when a modal closes
29+
- `Escape` closes all modals
30+
31+
### Keyboard navigation
32+
- All interactive elements reachable by `Tab`
33+
- Transaction rows are `role="button"` with `tabIndex={0}` and respond to `Enter`/`Space`
34+
- Global shortcuts: `Ctrl+N` (create account), `Escape` (close modals), `?` (shortcuts help)
35+
36+
### Screen reader support
37+
- `.sr-only` utility class for visually hidden but announced text
38+
- `<time dateTime="…">` for message timestamps
39+
- `role="list"` / `role="listitem"` on balance and transaction lists
40+
- `role="log"` on message history panel
41+
- `role="status"` on PWA banners and WebSocket indicator
42+
43+
### Color contrast
44+
- CSS custom properties (`--text`, `--primary`, `--danger`, `--success`) maintain ≥ 4.5:1 contrast ratio in both light and dark themes
45+
- Focus ring: `outline: 3px solid var(--primary)` via `:focus-visible`
46+
47+
## Testing
48+
49+
### Manual
50+
1. Tab through the entire app — every interactive element must be reachable and have a visible focus ring
51+
2. Activate all buttons/links with `Enter` and `Space`
52+
3. Open QR modal and transaction detail modal — confirm focus is trapped and returns on close
53+
4. Test with a screen reader (NVDA/JAWS on Windows, VoiceOver on macOS/iOS)
54+
55+
### Automated
56+
The existing `frontend/tests/accessibility.test.jsx` uses `@testing-library/jest-dom` assertions.
57+
Run with:
58+
```bash
59+
cd frontend && npm test
60+
```
61+
62+
To add axe-core automated checks, install `@axe-core/react` and add to `src/setupTests.js`:
63+
```js
64+
import { configureAxe } from '@axe-core/react';
65+
configureAxe(React, ReactDOM);
66+
```

0 commit comments

Comments
 (0)