AI Skill Library

Web Accessibility (a11y)

WCAG 2.2, ARIA roles, keyboard navigation, screen readers, focus management.

a11yaccessibilityfrontendhtml
# Web Accessibility (a11y)

## WCAG 2.2 principles (POUR)
- **Perceivable**: content visible/audible to all senses
- **Operable**: navigable by keyboard, no seizure triggers
- **Understandable**: readable, predictable behavior
- **Robust**: works with assistive technologies

## Semantic HTML (first line of defense)
```html
<!-- Use elements for their meaning -->
<header>, <nav>, <main>, <aside>, <footer>
<article>, <section>, <figure>, <figcaption>
<button> (not <div onclick>)
<a href> (not <span onclick>)
<ul>/<ol> for lists, <table> for tabular data
```

## ARIA
```html
<!-- Only add ARIA when semantic HTML isn't enough -->
<div role="dialog" aria-modal="true" aria-labelledby="title">
<button aria-expanded="false" aria-controls="menu">
<div role="alert" aria-live="polite">Error message</div>
<img alt="Descriptive text" />  <!-- empty alt="" for decorative -->

<!-- Progress/status -->
<div role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">

<!-- Required/invalid -->
<input aria-required="true" aria-invalid="true" aria-describedby="err">
<span id="err">Email is required</span>
```

## Keyboard navigation
- Every interactive element must be reachable via Tab.
- Focus order must be logical (match visual order).
- Provide visible focus indicator (never `outline: none` without replacement).
- Keyboard shortcuts: Enter/Space = activate, Esc = close, Arrow keys = navigate within widget.

```css
/* Visible focus */
:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}
```

## Focus management
```js
// Move focus to modal on open
dialog.addEventListener('open', () => firstFocusable.focus())

// Trap focus inside modal
document.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault(); last.focus()
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault(); first.focus()
    }
  }
})

// Return focus on close
const trigger = document.activeElement
dialog.close()
trigger.focus()
```

## Color & contrast
- Normal text: 4.5:1 contrast ratio (WCAG AA)
- Large text (18px+ or 14px bold): 3:1
- Non-text (icons, borders): 3:1
- Never convey information by color alone

## Testing
- Browser: axe DevTools, Wave, Lighthouse accessibility audit
- Keyboard: tab through entire page manually
- Screen reader: NVDA (Windows), VoiceOver (Mac/iOS), TalkBack (Android)
- `eslint-plugin-jsx-a11y` for React

API: /api/skills/web-accessibility