---
name: Accessibility Engineer
description: Implement accessibility (a11y) best practices to make applications usable by everyone. Use when building UIs, conducting accessibility audits, or ensuring WCAG compliance. Covers screen readers, keyboard navigation, ARIA attributes, and inclusive design patterns.
version: 1.0.0
---
# Accessibility Engineer
Build for everyone - accessibility is not optional.
## Core Principle
**Accessibility is a civil right, not a feature.**
1 in 4 adults in the US has a disability. Accessible design benefits everyone:
- Blind users (screen readers)
- Low vision users (zoom, high contrast)
- Deaf users (captions)
- Motor disabilities (keyboard-only)
- Cognitive disabilities (clear language)
- Temporary disabilities (broken arm)
- Situational limitations (bright sunlight, noisy environment)
## WCAG Compliance Levels
**Level A:** Minimum (legal requirement)
**Level AA:** Industry standard (aim for this)
**Level AAA:** Gold standard (difficult to achieve for all content)
**Target: WCAG 2.1 AA compliance**
---
## Pillar 1: Semantic HTML
### Use the Right Elements
```jsx
// ❌ Bad: Divs for everything (no semantic meaning)
// Skipped h2, h3
```
### Landmarks
```jsx
{/* Main content */}
```
---
## Pillar 2: Keyboard Navigation
### All Interactive Elements Must Be Keyboard Accessible
```jsx
// ✅ Button is keyboard accessible by default
// ❌ Div requires extra work
Click me
// Can't tab to it!
// ✅ If you must use div, add keyboard support
{
if (e.key === 'Enter' || e.key === ' ') {
handleClick()
}
}}
>
Click me
```
### Tab Order
```jsx
// ✅ Natural tab order (follows DOM order)
Help
// ❌ Don't use tabIndex > 0 (breaks natural order)
// Anti-pattern!
// ✅ tabIndex=-1 to remove from tab order
Not keyboard focusable
```
### Focus Management
```jsx
// Modal: Trap focus inside
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef()
useEffect(() => {
if (!isOpen) return
// Focus first focusable element
const firstFocusable = modalRef.current.querySelector('button, input, a')
firstFocusable?.focus()
// Trap focus
function handleTab(e) {
if (e.key !== 'Tab') return
const focusableElements = modalRef.current.querySelectorAll(
'button, input, a, [tabindex]:not([tabindex="-1"])'
)
const first = focusableElements[0]
const last = focusableElements[focusableElements.length - 1]
if (e.shiftKey) {
if (document.activeElement === first) {
last.focus()
e.preventDefault()
}
} else {
if (document.activeElement === last) {
first.focus()
e.preventDefault()
}
}
}
document.addEventListener('keydown', handleTab)
return () => document.removeEventListener('keydown', handleTab)
}, [isOpen])
return isOpen ? (
{children}
) : null
}
```
### Skip Links
```jsx
// Allow keyboard users to skip navigation
Skip to main content
{/* Main content */}
// CSS
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
}
.skip-link:focus {
top: 0;
}
```
---
## Pillar 3: ARIA Attributes
### Only Use ARIA When Semantic HTML Isn't Enough
```jsx
// ✅ Semantic HTML (no ARIA needed)
// ❌ Unnecessary ARIA
// ✅ ARIA needed (custom widget)
Tab 1
```
### Common ARIA Attributes
**aria-label** - Provides accessible name:
```jsx
```
**aria-labelledby** - References another element:
```jsx