Skip to content

Commit 7de7f24

Browse files
committed
chore(a11y): add accessibility skill
1 parent 16300aa commit 7de7f24

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
name: accessibility
3+
description: Maintain WCAG-focused accessibility in stream-chat-react. Use when changing interactive components, dialogs, menus, forms, media controls, notifications, focus behavior, keyboard flows, aria attributes, or screen-reader announcements.
4+
---
5+
6+
# Accessibility Maintenance (stream-chat-react)
7+
8+
Use this skill whenever code changes can affect keyboard users, screen readers, focus behavior, motion preferences, or semantic HTML/ARIA.
9+
10+
## Non-negotiable rules
11+
12+
1. Prefer native semantics first (`button`, `input`, `label`, `img`, etc.). Use ARIA roles only when native semantics cannot represent the widget.
13+
2. Do not add hardcoded English accessibility labels. Use i18n keys (`t('aria/...')`) for user-facing `aria-label`/`aria-description`/announcement strings.
14+
3. Keep one focusable interactive target per action. Avoid duplicate focus stops and nested-interactive patterns.
15+
4. If a control is keyboard-activatable, support Enter/Space behavior and visible focus.
16+
5. Decorative visuals stay hidden from AT (`aria-hidden`, `focusable="false"` for SVG icons).
17+
6. Keep changes additive and backward-compatible for SDK consumers.
18+
19+
## Where to put what
20+
21+
- **Cross-cutting accessibility decisions and scope changes**
22+
- `specs/wcag-compliance/decisions.md` (append-only decision log)
23+
- `specs/wcag-compliance/plan.md` and `specs/wcag-compliance/state.json` (task tracking)
24+
- **Shared a11y primitives**
25+
- `src/components/Accessibility/` (`AriaLiveRegion`, announcer hooks, notification announcer)
26+
- `src/components/VisuallyHidden/`
27+
- **Global reduced-motion/focus behavior styles**
28+
- `src/styling/accessibility.scss`
29+
- `src/styling/base.scss` (shared focus tokens)
30+
- **Component-level semantics and keyboard handling**
31+
- in the component itself under `src/components/**`
32+
- **Translations for new `aria/...` keys**
33+
- all locale files in `src/i18n/*.json` (never leave empty values)
34+
- **Tests**
35+
- nearest component test folder: `src/components/**/__tests__/`
36+
- include `jest-axe` checks for semantic/ARIA-sensitive changes
37+
38+
## Patterns to follow
39+
40+
### 1) Accessible names and descriptions
41+
42+
- Prefer `aria-labelledby` when visible label text exists.
43+
- Use `aria-label` only as fallback when label-by-id is not available.
44+
- For dialog surfaces:
45+
- provide `role` and `aria-modal` for modal behavior
46+
- wire `aria-labelledby` and `aria-describedby` to visible title/description nodes
47+
- For images:
48+
- always render `alt` (`''` when decorative)
49+
50+
### 2) Keyboard behavior
51+
52+
- Native controls: rely on native keyboard behavior whenever possible.
53+
- Non-native interactive wrappers (only when unavoidable):
54+
- `role="button"`
55+
- `tabIndex={0}`
56+
- `onKeyDown` for Enter/Space activation
57+
- prevent default on Space when needed to avoid scroll side-effects
58+
- Menus/listboxes/tabs:
59+
- use role-appropriate child roles (`menu`/`menuitem`, `listbox`/`option`, `tablist`/`tab`/`tabpanel`)
60+
- keep role/attribute combinations valid (example: `menuitemradio` with `aria-checked`)
61+
62+
### 3) Live regions and announcements
63+
64+
- Prefer centralized announcers over ad-hoc `aria-live` scattered across components:
65+
- `AriaLiveRegion` + `useAriaLiveAnnouncer`
66+
- `NotificationAnnouncer`
67+
- Use `polite` for non-urgent updates; `assertive` for urgent/error updates.
68+
- For repeated announcements, clear then set message (small delay) to force re-announcement.
69+
- For modals, do not use live regions for static body content; rely on correct dialog semantics + focus management.
70+
71+
### 4) Focus management
72+
73+
- Maintain visible focus indicators (do not remove outlines without replacement).
74+
- When trapping focus in dialogs, ensure focus enters the dialog and is restored on close.
75+
- After closing transient dialogs/popovers, restore focus to the invoking trigger when expected.
76+
77+
### 5) Motion preferences
78+
79+
- Respect `prefers-reduced-motion` in both CSS and JS behavior:
80+
- CSS transitions/animations minimized in `accessibility.scss`
81+
- JS-driven scrolling/animation behavior downgraded to non-smooth where needed
82+
83+
## ARIA attribute guardrails
84+
85+
- Use ARIA as contract, not decoration:
86+
- if role implies state, provide matching state attribute (`aria-selected`, `aria-checked`, etc.) only when valid for that role
87+
- never attach unsupported attributes to roles just to satisfy a visual state
88+
- `aria-hidden` is for decorative/non-essential content only, never for focusable controls.
89+
- Icon-only controls must carry an accessible name on the control element itself.
90+
91+
## i18n rules for accessibility text
92+
93+
1. New accessibility labels/announcements must use `t('aria/...')` or established translation topics.
94+
2. Add keys to all locales in `src/i18n/*.json`.
95+
3. For dynamic values, always use i18n interpolation syntax (for example `t('aria/{{count}} new messages', { count })` or equivalent existing key shape), never string concatenation.
96+
4. Run translation validation/lint flow; no empty translation values.
97+
98+
## Testing requirements per accessibility change
99+
100+
Minimum:
101+
102+
- unit tests for new keyboard/focus/semantics behavior in nearest `__tests__` folder
103+
- one `jest-axe` assertion for components where semantics changed
104+
105+
Recommended:
106+
107+
- regression tests for:
108+
- Enter/Space activation
109+
- role/state attributes
110+
- focus restore on close
111+
- reduced-motion behavior where logic branches in JS
112+
113+
## Execution workflow (copy this checklist)
114+
115+
- [ ] Identify the interaction type (button/menu/dialog/listbox/form/slider/live region)
116+
- [ ] Choose native element first; fall back to ARIA only if necessary
117+
- [ ] Add or correct label wiring (`aria-labelledby` preferred, `aria-label` fallback)
118+
- [ ] Verify keyboard path (Tab + Enter/Space + arrow keys where pattern requires)
119+
- [ ] Verify focus visibility and focus restore behavior
120+
- [ ] Ensure decorative visuals are hidden from AT and icon-only controls are named
121+
- [ ] Add/update i18n keys for new accessibility text across all locales
122+
- [ ] Add/update tests (`jest-axe` where semantics changed)
123+
- [ ] Append rationale to `specs/wcag-compliance/decisions.md` for cross-cutting decisions
124+
125+
## Common mistakes to avoid
126+
127+
- Hardcoded English `aria-label` values in component code.
128+
- Adding `tabIndex`/roles to containers that only capture backdrop clicks.
129+
- Creating two focusable wrappers for one action path.
130+
- Introducing invalid role/attribute pairs (for example `aria-selected` on plain buttons).
131+
- Using live regions to force modal text announcement instead of fixing dialog semantics.

0 commit comments

Comments
 (0)