---
name: accessibility
description: Expert accessibility implementation with WCAG compliance, screen reader support, and inclusive design
---

# Accessibility Specialist

You are an expert in web accessibility. Apply these principles to create inclusive experiences.

## Core Philosophy

- **Perceivable** — Information must be presentable in ways users can perceive
- **Operable** — Interface must be operable by all users
- **Understandable** — Information and operation must be understandable
- **Robust** — Content must be robust enough for assistive technologies

## Semantic HTML

### Use Correct Elements
```html
<!-- ✅ Semantic elements -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Article Title</h1>
    <p>Content...</p>
  </article>
  
  <aside aria-label="Related articles">
    <h2>Related</h2>
    <!-- ... -->
  </aside>
</main>

<footer>
  <p>&copy; 2024 Company</p>
</footer>

<!-- ❌ Div soup -->
<div class="header">
  <div class="nav">
    <div class="nav-item" onclick="navigate('/')">Home</div>
  </div>
</div>
```

### Heading Hierarchy
```html
<!-- ✅ Correct hierarchy -->
<h1>Page Title</h1>
  <h2>Section 1</h2>
    <h3>Subsection 1.1</h3>
    <h3>Subsection 1.2</h3>
  <h2>Section 2</h2>
    <h3>Subsection 2.1</h3>

<!-- ❌ Skipping levels -->
<h1>Page Title</h1>
<h4>This skips h2 and h3!</h4>

<!-- ❌ Using headings for styling -->
<h3>This isn't a heading, just wanted big text</h3>
```

### Interactive Elements
```html
<!-- ✅ Use buttons for actions -->
<button type="button" onclick="doSomething()">Click me</button>

<!-- ✅ Use links for navigation -->
<a href="/other-page">Go to other page</a>

<!-- ❌ Divs with click handlers -->
<div onclick="doSomething()">Click me</div>

<!-- ❌ Links that don't go anywhere -->
<a href="#" onclick="doSomething()">Click me</a>
<a href="javascript:void(0)">Click me</a>
```

## Keyboard Navigation

### Focus Management
```css
/* ✅ Visible focus indicator */
:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* ✅ Remove outline only if custom indicator exists */
:focus:not(:focus-visible) {
  outline: none;
}

:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 95, 204, 0.2);
}

/* ❌ Never do this without replacement */
:focus {
  outline: none; /* Breaks keyboard navigation! */
}
```

### Tab Order
```html
<!-- ✅ Natural tab order follows DOM -->
<form>
  <input type="text" name="first" />    <!-- Tab 1 -->
  <input type="text" name="last" />     <!-- Tab 2 -->
  <button type="submit">Submit</button> <!-- Tab 3 -->
</form>

<!-- Use tabindex sparingly -->
<button tabindex="0">Normal tab order</button>
<div tabindex="0">Make focusable</div>
<button tabindex="-1">Programmatic focus only</button>

<!-- ❌ Avoid positive tabindex -->
<input tabindex="2" />  <!-- Creates confusing order -->
<input tabindex="1" />
```

### Keyboard Shortcuts
```javascript
// ✅ Skip links for keyboard users
<a href="#main-content" class="skip-link">Skip to main content</a>

// CSS
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}

// ✅ Handle common keyboard interactions
function handleKeyDown(event) {
  switch (event.key) {
    case 'Enter':
    case ' ':
      activateItem()
      event.preventDefault()
      break
    case 'Escape':
      closeModal()
      break
    case 'ArrowDown':
      focusNextItem()
      event.preventDefault()
      break
    case 'ArrowUp':
      focusPreviousItem()
      event.preventDefault()
      break
  }
}
```

## ARIA

### When to Use ARIA
```html
<!-- Rule: No ARIA is better than bad ARIA -->

<!-- ✅ Native HTML first -->
<button>Click me</button>  <!-- No ARIA needed! -->

<!-- ✅ ARIA when HTML can't express it -->
<div 
  role="tablist"
  aria-label="Account settings"
>
  <button 
    role="tab" 
    aria-selected="true"
    aria-controls="panel-1"
  >
    Profile
  </button>
  <button 
    role="tab" 
    aria-selected="false"
    aria-controls="panel-2"
  >
    Security
  </button>
</div>

<!-- ❌ Redundant ARIA -->
<button role="button">Click me</button>
<a role="link" href="/page">Link</a>
```

### Common ARIA Patterns

#### Live Regions
```html
<!-- Announce dynamic content changes -->
<div aria-live="polite" aria-atomic="true">
  <!-- Content changes here will be announced -->
  Items in cart: 3
</div>

<!-- For urgent announcements -->
<div role="alert">
  Error: Please fill in required fields
</div>

<!-- For status messages -->
<div role="status">
  Your changes have been saved
</div>
```

#### Modals
```html
<div 
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-desc"
>
  <h2 id="modal-title">Confirm Delete</h2>
  <p id="modal-desc">Are you sure you want to delete this item?</p>
  <button>Cancel</button>
  <button>Delete</button>
</div>
```

```javascript
// Focus management for modals
function openModal(modal) {
  // Store previously focused element
  previouslyFocused = document.activeElement
  
  // Focus first focusable element in modal
  modal.querySelector('button, input, [tabindex="0"]').focus()
  
  // Trap focus inside modal
  modal.addEventListener('keydown', trapFocus)
}

function closeModal(modal) {
  // Return focus to trigger element
  previouslyFocused.focus()
}
```

## Forms

### Labels and Instructions
```html
<!-- ✅ Explicit labels -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" />

<!-- ✅ With help text -->
<label for="password">Password</label>
<input 
  type="password" 
  id="password"
  aria-describedby="password-help password-error"
/>
<p id="password-help">Must be at least 8 characters</p>
<p id="password-error" role="alert" class="error">
  Password is too weak
</p>

<!-- ✅ Required fields -->
<label for="name">
  Name <span aria-hidden="true">*</span>
  <span class="sr-only">(required)</span>
</label>
<input type="text" id="name" required aria-required="true" />

<!-- ❌ Placeholder as label -->
<input type="email" placeholder="Email" />  <!-- No label! -->
```

### Error Handling
```html
<!-- ✅ Associate errors with inputs -->
<label for="email">Email</label>
<input 
  type="email" 
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error" role="alert" class="error">
  Please enter a valid email address
</p>

<!-- ✅ Form-level error summary -->
<div role="alert" aria-labelledby="error-heading">
  <h2 id="error-heading">Please fix the following errors:</h2>
  <ul>
    <li><a href="#email">Email is invalid</a></li>
    <li><a href="#password">Password is required</a></li>
  </ul>
</div>
```

## Images and Media

### Alt Text
```html
<!-- ✅ Informative images -->
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2" />

<!-- ✅ Decorative images -->
<img src="decoration.png" alt="" role="presentation" />

<!-- ✅ Images with text -->
<img src="logo.png" alt="Company Name" />

<!-- ✅ Complex images -->
<figure>
  <img src="diagram.png" alt="System architecture diagram" />
  <figcaption>
    Figure 1: The system consists of three main components...
  </figcaption>
</figure>

<!-- ❌ Bad alt text -->
<img src="photo.jpg" alt="image" />
<img src="photo.jpg" alt="photo.jpg" />
<img src="photo.jpg" />  <!-- Missing alt! -->
```

### Video and Audio
```html
<video controls>
  <source src="video.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" />
  <track kind="descriptions" src="descriptions.vtt" srclang="en" label="Audio descriptions" />
  Your browser doesn't support video.
</video>

<!-- Provide transcript -->
<details>
  <summary>Video transcript</summary>
  <p>Full transcript of the video content...</p>
</details>
```

## Color and Contrast

### Minimum Contrast Ratios
```css
/* WCAG AA Requirements */
/* Normal text: 4.5:1 */
/* Large text (18pt+ or 14pt+ bold): 3:1 */
/* UI components and graphics: 3:1 */

/* ✅ Good contrast */
.text {
  color: #333;          /* On white: 12.6:1 ✓ */
  background: #fff;
}

.button {
  background: #0066cc;  /* On white: 4.5:1 ✓ */
  color: #fff;
}

/* ❌ Poor contrast */
.light-text {
  color: #999;          /* On white: 2.8:1 ✗ */
  background: #fff;
}
```

### Don't Rely on Color Alone
```html
<!-- ❌ Color only -->
<span style="color: red">Error</span>
<span style="color: green">Success</span>

<!-- ✅ Color + icon + text -->
<span class="error">
  <svg aria-hidden="true"><!-- error icon --></svg>
  Error: Invalid email
</span>

<!-- ✅ Links with more than color -->
<a href="/page" class="underline">Visit page</a>
```

## Testing

### Automated Testing
```javascript
// Use axe-core for automated checks
import { axe, toHaveNoViolations } from 'jest-axe'

expect.extend(toHaveNoViolations)

test('page has no accessibility violations', async () => {
  const { container } = render(<MyComponent />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})
```

### Manual Testing Checklist
```markdown
- [ ] Navigate entire page with keyboard only
- [ ] Test with screen reader (VoiceOver, NVDA)
- [ ] Check focus indicators are visible
- [ ] Verify color contrast (use browser tools)
- [ ] Test at 200% zoom
- [ ] Check with reduced motion preference
- [ ] Test form error handling
- [ ] Verify all images have appropriate alt text
```

## Anti-Patterns

### ❌ Inaccessible Custom Components
```jsx
// Bad: Custom dropdown without accessibility
<div class="dropdown" onClick={toggle}>
  <div class="selected">{value}</div>
  <div class="options">
    {options.map(opt => (
      <div onClick={() => select(opt)}>{opt}</div>
    ))}
  </div>
</div>

// Good: Accessible custom dropdown
<div 
  role="listbox"
  aria-label="Select option"
  aria-activedescendant={activeId}
  tabIndex={0}
  onKeyDown={handleKeyDown}
>
  {options.map(opt => (
    <div 
      role="option"
      id={opt.id}
      aria-selected={opt === value}
      onClick={() => select(opt)}
    >
      {opt}
    </div>
  ))}
</div>
```

### ❌ Auto-playing Media
```html
<!-- Bad: Auto-play with sound -->
<video autoplay>

<!-- Better: Muted autoplay -->
<video autoplay muted>

<!-- Best: User-initiated -->
<video controls>
```

### ❌ Motion Without User Control
```css
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
```
