--- name: accessibility-aria-expert description: Detects and fixes accessibility issues in React/Fluent UI webviews. Use when reviewing code for screen reader compatibility, fixing ARIA labels, ensuring keyboard navigation, adding live regions for status messages, or managing focus in dialogs. --- # Accessibility Expert for Webviews Verify and fix accessibility in React/Fluent UI webview components. ## When to Use - Review webview code for accessibility issues - Fix double announcements from screen readers - Add missing `aria-label` to icon-only buttons or form inputs - Make tooltips accessible to keyboard/screen reader users - Announce status changes (loading, search results, errors) - Manage focus when dialogs/modals open - Group related controls with proper labels ## Core Pattern: Tooltip Accessibility Tooltips require `aria-label` + `aria-hidden` to avoid double announcements: ```tsx ``` - `aria-label`: Full context (visible text + tooltip) - `aria-hidden="true"`: Wraps visible text to prevent duplication - Screen reader hears: "Badge text. Detailed explanation" ## Detection Rules ### 1. Tooltip Without aria-label Context ❌ **Problem**: Tooltip content inaccessible to screen readers ```tsx ``` ✅ **Fix**: Include tooltip in aria-label ```tsx ``` ### 2. Missing aria-hidden (Double Announcement) ❌ **Problem**: Screen reader says "Collection scan Collection scan" ```tsx Collection scan ``` ✅ **Fix**: Wrap visible text ```tsx ``` ### 3. Redundant aria-label (NOT Needed) ❌ **Problem**: aria-label identical to visible text adds no value ```tsx }>Validate ``` ✅ **Fix**: Remove redundant aria-label OR make it more descriptive ```tsx }>Validate ``` **Keep aria-label only when it adds information:** ```tsx }> Save ``` ### 4. Icon-Only Button Missing aria-label ❌ **Problem**: No accessible name ```tsx } onClick={onDelete} /> ``` ✅ **Fix**: Add aria-label ```tsx } onClick={onDelete} /> ``` ### 5. Decorative Elements Not Hidden ❌ **Problem**: Progress bar announced unnecessarily ```tsx ``` ✅ **Fix**: Hide decorative elements ```tsx ``` ### 6. Input Missing Accessible Name ❌ **Problem**: SpinButton/Input without accessible name ```tsx ``` ✅ **Fix**: Add aria-label or associate with label element ```tsx ``` ### 7. Visible Label Not in Accessible Name ❌ **Problem**: aria-label doesn't contain visible text (breaks voice control) ```tsx }> Refresh ``` ✅ **Fix**: Accessible name must contain visible label exactly ```tsx }> Refresh ``` Voice control users say "click Refresh" – only works if accessible name contains "Refresh". ### 8. Status Changes Not Announced ❌ **Problem**: Screen reader doesn't announce dynamic content ```tsx {isLoading ? 'Loading...' : `${count} results`} ``` ✅ **Fix**: Use the `Announcer` component ```tsx import { Announcer } from '../../api/webview-client/accessibility'; // Announces when `when` transitions from false to true // Dynamic message based on state 0 ? l10n.t('Results found') : l10n.t('No results found')} /> ``` Use for: loading states, search results, success/error messages. ### 9. Dialog Opens Without Focus Move ❌ **Problem**: Focus stays on trigger when modal opens ```tsx { isOpen && ...; } ``` ✅ **Fix**: Move focus programmatically ```tsx const dialogRef = useRef(null); useEffect(() => { if (isOpen) dialogRef.current?.focus(); }, [isOpen]); { isOpen && ( ... ); } ``` ### 10. Related Controls Without Group Label ❌ **Problem**: Buttons share visual label but screen reader misses context ```tsx How would you rate this? ``` ✅ **Fix**: Use role="group" with aria-labelledby ```tsx
How would you rate this?
``` ## When to Use aria-hidden **DO use** on: - Visible text when aria-label provides complete context - Decorative icons, spinners, progress bars - Visual separators (\`|\`, \`—\`) **DO NOT use** on: - The only accessible content (hides it completely) - Interactive/focusable elements - Error messages or alerts ## focusableBadge Pattern For keyboard-accessible badges with tooltips: 1. Import: \`import '../components/focusableBadge/focusableBadge.scss';\` 2. Apply attributes: ```tsx ``` ## Screen Reader Announcements Use the `Announcer` component for WCAG 4.1.3 (Status Messages) compliance. ```tsx import { Announcer } from '../../api/webview-client/accessibility'; ``` ### Basic Usage ```tsx // Announces "AI is analyzing..." when isLoading becomes true // Dynamic message based on state (e.g., query results) 0 ? l10n.t('Results found') : l10n.t('No results found')} /> // With assertive politeness (default is polite) ``` ### Props - `when`: Announces when this transitions from `false` to `true` - `message`: The message to announce (use `l10n.t()` for localization) - `politeness`: `'assertive'` (default, interrupts) or `'polite'` (waits for idle) ### Key Points - **Placement doesn't matter** - screen readers monitor all live regions regardless of DOM position; place near related UI for code readability - **Store relevant state** (e.g., `documentCount`) to derive dynamic messages - **Use `l10n.t()` for messages** - announcements must be localized - **Condition resets automatically** - when `when` goes back to `false`, it's ready for the next announcement - **Prefer 'assertive'** for user-initiated actions, 'polite' for background updates ## Quick Checklist - [ ] Icon-only buttons have `aria-label` - [ ] Form inputs have associated labels or `aria-label` - [ ] Tooltip content included in `aria-label` - [ ] Visible text wrapped in `aria-hidden="true"` when aria-label duplicates it - [ ] Redundant aria-labels removed (identical to visible text) - [ ] Visible button labels match accessible name exactly (for voice control) - [ ] Decorative elements have `aria-hidden={true}` - [ ] Badges with tooltips use `focusableBadge` class + `tabIndex={0}` - [ ] Status updates use `Announcer` component - [ ] Focus moves to dialog/modal content when opened - [ ] Related controls wrapped in `role="group"` with `aria-labelledby` ## References - [WCAG 2.1.1 Keyboard](https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html) - [WCAG 2.4.3 Focus Order](https://www.w3.org/WAI/WCAG21/Understanding/focus-order.html) - [WCAG 2.5.3 Label in Name](https://www.w3.org/WAI/WCAG21/Understanding/label-in-name.html) - [WCAG 4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html) - [WCAG 4.1.3 Status Messages](https://www.w3.org/WAI/WCAG21/Understanding/status-messages.html) - See `src/webviews/components/focusableBadge/focusableBadge.md` for the Badge pattern