# Accessibility Guide for LWC
Comprehensive guide to building WCAG 2.1 AA compliant Lightning Web Components.
---
## Table of Contents
1. [Accessibility Standards](#accessibility-standards)
2. [Semantic HTML](#semantic-html)
3. [ARIA Attributes](#aria-attributes)
4. [Keyboard Navigation](#keyboard-navigation)
5. [Focus Management](#focus-management)
6. [Color and Contrast](#color-and-contrast)
7. [Screen Reader Support](#screen-reader-support)
8. [Live Regions](#live-regions)
9. [Form Accessibility](#form-accessibility)
10. [Common Patterns](#common-patterns)
11. [Testing](#testing)
12. [Tools and Resources](#tools-and-resources)
---
## Accessibility Standards
### WCAG 2.1 AA Compliance
All Lightning Web Components should meet WCAG 2.1 Level AA standards.
| Principle | Description |
|-----------|-------------|
| **Perceivable** | Information must be presentable to users in ways they can perceive |
| **Operable** | UI components must be operable (keyboard, mouse, voice) |
| **Understandable** | Information and UI must be understandable |
| **Robust** | Content must work with assistive technologies |
### Key Requirements
| Requirement | Standard | Implementation |
|-------------|----------|----------------|
| **Color contrast** | 4.5:1 for normal text, 3:1 for large text | Use SLDS color tokens |
| **Keyboard navigation** | All interactive elements accessible via keyboard | Tab order, Enter/Space triggers |
| **Screen reader support** | ARIA labels, roles, live regions | Proper semantic HTML + ARIA |
| **Focus indicators** | Visible focus state | Use SLDS focus utilities |
| **Alternative text** | All images have alt text | `alt` attribute on images |
---
## Semantic HTML
### Use Proper HTML Elements
```html
Click me
Click me
```
### Headings Hierarchy
```html
Page Title
Subsection
Page Title
Main Section
Subsection
```
### Landmarks
```html
Account Details
```
---
## ARIA Attributes
### ARIA Labels
```html
Enter phone number with country code
```
### ARIA Roles
```html
Error
Form validation failed
Confirm Action
Are you sure you want to delete this record?
```
### ARIA States
```html
Show Details
```
---
## Keyboard Navigation
### Tab Order
```html
Skip to main content
```
### Keyboard Event Handlers
```javascript
// accountCard.js
export default class AccountCard extends LightningElement {
handleKeyDown(event) {
// Enter or Space activates
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleSelect();
}
// Arrow navigation
if (event.key === 'ArrowDown') {
event.preventDefault();
this.focusNextItem();
} else if (event.key === 'ArrowUp') {
event.preventDefault();
this.focusPreviousItem();
}
// Escape closes
if (event.key === 'Escape') {
this.handleClose();
}
}
focusNextItem() {
const items = this.template.querySelectorAll('[role="listitem"]');
const currentIndex = Array.from(items).indexOf(document.activeElement);
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex].focus();
}
focusPreviousItem() {
const items = this.template.querySelectorAll('[role="listitem"]');
const currentIndex = Array.from(items).indexOf(document.activeElement);
const prevIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1;
items[prevIndex].focus();
}
}
```
```html
```
---
## Focus Management
### Focus Trap in Modals
```javascript
// composableModal.js
export default class ComposableModal extends LightningElement {
_focusableElements = [];
_isOpen = false;
@api
toggleModal() {
this._isOpen = !this._isOpen;
if (this._isOpen) {
// Capture focusable elements
this._focusableElements = this.getFocusableElements();
// Focus first element
requestAnimationFrame(() => {
this._focusableElements[0]?.focus();
});
// Add keyboard listener
window.addEventListener('keydown', this._handleKeyDown);
// Store previous focus
this._previousFocus = document.activeElement;
} else {
// Remove keyboard listener
window.removeEventListener('keydown', this._handleKeyDown);
// Restore focus
this._previousFocus?.focus();
}
}
getFocusableElements() {
const selector = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])'
].join(',');
return Array.from(this.template.querySelectorAll(selector));
}
_handleKeyDown = (event) => {
if (event.key === 'Tab') {
this.trapFocus(event);
} else if (event.key === 'Escape') {
this.toggleModal();
}
}
trapFocus(event) {
const firstElement = this._focusableElements[0];
const lastElement = this._focusableElements[this._focusableElements.length - 1];
const activeElement = this.template.activeElement;
if (event.shiftKey) {
// Shift+Tab: Moving backward
if (activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else {
// Tab: Moving forward
if (activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
}
disconnectedCallback() {
window.removeEventListener('keydown', this._handleKeyDown);
}
}
```
### Managing Focus After Actions
```javascript
handleDelete(event) {
const itemId = event.target.dataset.id;
const itemIndex = this.items.findIndex(item => item.Id === itemId);
// Delete item
this.items = this.items.filter(item => item.Id !== itemId);
// Focus next item or previous if last item deleted
requestAnimationFrame(() => {
const focusIndex = itemIndex < this.items.length ? itemIndex : itemIndex - 1;
if (focusIndex >= 0) {
const nextItem = this.template.querySelector(`[data-id="${this.items[focusIndex].Id}"]`);
nextItem?.focus();
}
});
}
```
---
## Color and Contrast
### Use SLDS Color Tokens
```css
/* BAD: Hardcoded colors */
.my-component {
color: #333333;
background-color: #ffffff;
border-color: #dddddd;
}
/* GOOD: SLDS tokens with proper contrast */
.my-component {
color: var(--slds-g-color-on-surface, #181818);
background-color: var(--slds-g-color-surface-container-1, #ffffff);
border-color: var(--slds-g-color-border-1, #c9c9c9);
}
```
### Testing Contrast
```html
Regular body text
Large heading
Visit our help center for support.
```
### Color Independence
```html
Error
Error
Failed
```
---
## Screen Reader Support
### Assistive Text
```html
Required field
Edit
Loading data, please wait
```
### Image Alternative Text
```html
```
---
## Live Regions
### ARIA Live Regions
```javascript
// notificationComponent.js
export default class NotificationComponent extends LightningElement {
@track messages = [];
addMessage(message, type = 'info') {
const id = Date.now();
this.messages = [...this.messages, { id, message, type }];
// Auto-remove after 5 seconds
setTimeout(() => {
this.messages = this.messages.filter(m => m.id !== id);
}, 5000);
}
}
```
```html
```
### Assertive vs Polite
```html
{searchResultsCount} results found
Error
Form submission failed. Please correct the errors and try again.
```
---
## Form Accessibility
### Accessible Form Fields
```html
```
### Fieldset and Legend
```html
Contact Method *
```
---
## Common Patterns
### Accessible Tabs
```javascript
// tabsComponent.js
export default class TabsComponent extends LightningElement {
@track activeTab = 'tab1';
handleTabKeyDown(event) {
const tabs = Array.from(this.template.querySelectorAll('[role="tab"]'));
const currentIndex = tabs.indexOf(event.target);
let nextIndex;
if (event.key === 'ArrowRight') {
nextIndex = (currentIndex + 1) % tabs.length;
} else if (event.key === 'ArrowLeft') {
nextIndex = currentIndex === 0 ? tabs.length - 1 : currentIndex - 1;
} else if (event.key === 'Home') {
nextIndex = 0;
} else if (event.key === 'End') {
nextIndex = tabs.length - 1;
}
if (nextIndex !== undefined) {
event.preventDefault();
tabs[nextIndex].focus();
this.activeTab = tabs[nextIndex].dataset.tab;
}
}
handleTabClick(event) {
this.activeTab = event.currentTarget.dataset.tab;
}
}
```
```html
```
### Accessible Data Table
```html
```
---
## Testing
### Automated Testing
```javascript
// Jest accessibility tests
it('has proper ARIA labels', () => {
const element = createElement('c-my-component', {
is: MyComponent
});
document.body.appendChild(element);
const button = element.shadowRoot.querySelector('button');
expect(button.getAttribute('aria-label')).toBeTruthy();
});
it('manages focus when modal opens', async () => {
const element = createElement('c-modal', { is: Modal });
document.body.appendChild(element);
element.openModal();
await flushPromises();
const firstFocusable = element.shadowRoot.querySelector('.focusable');
expect(document.activeElement).toBe(firstFocusable);
});
it('announces status changes to screen readers', async () => {
const element = createElement('c-notification', {
is: Notification
});
document.body.appendChild(element);
element.showMessage('Success');
await flushPromises();
const liveRegion = element.shadowRoot.querySelector('[aria-live]');
expect(liveRegion.textContent).toContain('Success');
});
```
### Manual Testing Checklist
- [ ] Navigate entire component using only keyboard (Tab, Shift+Tab, Enter, Space, Arrows)
- [ ] Test with screen reader (NVDA, JAWS, VoiceOver)
- [ ] Verify color contrast ratios (4.5:1 minimum for text)
- [ ] Test at 200% zoom
- [ ] Verify focus indicators are visible
- [ ] Test with high contrast mode
- [ ] Verify all interactive elements have accessible names
- [ ] Test form validation announcements
---
## Tools and Resources
### Browser Extensions
| Tool | Purpose |
|------|---------|
| **axe DevTools** | Automated accessibility testing |
| **Lighthouse** | Built into Chrome DevTools, accessibility audit |
| **WAVE** | Visual accessibility evaluation |
| **Color Contrast Analyzer** | Check WCAG contrast compliance |
### Screen Readers
| Platform | Screen Reader |
|----------|---------------|
| Windows | NVDA (free), JAWS |
| macOS | VoiceOver (built-in) |
| iOS | VoiceOver (built-in) |
| Android | TalkBack (built-in) |
### Testing Commands
```bash
# Run axe accessibility tests
npm install --save-dev @axe-core/cli
axe https://your-app.lightning.force.com
# Lighthouse CLI
npm install -g lighthouse
lighthouse https://your-app.lightning.force.com --only-categories=accessibility
```
### Resources
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/)
- [Salesforce Lightning Design System Accessibility](https://www.lightningdesignsystem.com/accessibility/overview/)
- [WebAIM Resources](https://webaim.org/resources/)
---
## Related Resources
- [component-patterns.md](component-patterns.md) - Implementation patterns
- [jest-testing.md](jest-testing.md) - Testing strategies
- [performance-guide.md](performance-guide.md) - Performance optimization