---
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
Badge text
```
- `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
Collection scan
```
### 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
Visible text
```
## 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