Files
2025-11-30 08:58:02 +08:00

557 lines
12 KiB
Markdown

# Accessibility Guidelines
WCAG 2.1 Level AA compliance checklist and best practices for web design.
## Color & Contrast
### Text Contrast Requirements
**Normal text (< 18px or < 14px bold):**
- Minimum contrast ratio: 4.5:1 against background
- Example: #18181b text on #ffffff = 19.56:1 ✓
- Example: #71717a text on #fafafa = 2.8:1 ✗
**Large text (≥ 18px or ≥ 14px bold):**
- Minimum contrast ratio: 3:1 against background
- Recommended: Still aim for 4.5:1 when possible
**UI Components & Graphics:**
- Interactive elements: 3:1 against adjacent colors
- Graphs, charts, icons: 3:1 minimum
- Focus indicators: 3:1 against background
### Tools for Testing
Use these tools to verify contrast:
- WebAIM Contrast Checker
- Chrome DevTools Lighthouse
- Stark plugin (Figma/Sketch)
- Contrast Analyzer (desktop app)
### Common Issues
**Insufficient contrast:**
- Light gray text on white (#aaa on #fff = 2.3:1)
- Placeholder text often fails (many browsers use low contrast)
- Disabled states (okay to have lower contrast, but clearly indicate disabled)
**Good practices:**
- Text on images: Add overlay or shadow for contrast
- Links: Underline or sufficient contrast difference
- Buttons: Ensure text contrasts with background
## Semantic HTML
### Use Appropriate Elements
**Navigation:**
```html
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
```
**Main content:**
```html
<main>
<article>
<h1>Page Title</h1>
<p>Content...</p>
</article>
</main>
```
**Complementary content:**
```html
<aside>
<h2>Related Links</h2>
</aside>
```
**Page sections:**
```html
<section>
<h2>Section Title</h2>
</section>
```
**Buttons vs Links:**
- `<button>`: Actions (submit, toggle, trigger)
- `<a>`: Navigation to another page/location
### Heading Hierarchy
**Rules:**
- One `<h1>` per page (page title)
- Don't skip levels (h1 → h3 is wrong)
- Headings create document outline
**Good structure:**
```html
<h1>Main Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h3>Subsection 1.2</h3>
<h2>Section 2</h2>
```
### Lists
**Use lists for:**
- Navigation menus
- Steps/sequences
- Related items
- Features/benefits
**Types:**
- `<ul>`: Unordered (bullets)
- `<ol>`: Ordered (numbers)
- `<dl>`: Definition lists (term/description pairs)
## Keyboard Navigation
### Focus Management
**All interactive elements must be keyboard accessible:**
- Links (`<a>`)
- Buttons (`<button>`)
- Form inputs
- Custom interactive elements (add tabindex="0")
**Focus indicators must be visible:**
```css
button:focus-visible {
outline: 2px solid #0ea5e9;
outline-offset: 2px;
}
```
**Don't remove default focus without replacement:**
```css
/* BAD */
*:focus { outline: none; }
/* GOOD */
*:focus { outline: 2px solid #0ea5e9; }
```
### Tab Order
**Natural DOM order is best:**
- Don't use `tabindex` values > 0 (breaks natural flow)
- Use `tabindex="-1"` to remove from tab order when appropriate
- Use `tabindex="0"` to add custom elements to tab order
**Skip links for long navigation:**
```html
<a href="#main-content" class="skip-link">
Skip to main content
</a>
```
```css
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
}
.skip-link:focus {
top: 0;
}
```
### Keyboard Shortcuts
**Essential interactions:**
- **Tab**: Move forward through interactive elements
- **Shift+Tab**: Move backward
- **Enter**: Activate links/buttons
- **Space**: Activate buttons, checkboxes
- **Escape**: Close modals/dropdowns
- **Arrow keys**: Navigate within components (tabs, menus, sliders)
**Custom interactions:**
Document any custom keyboard shortcuts clearly in UI.
## Screen Readers
### Alternative Text
**Images:**
```html
<!-- Informative image -->
<img src="chart.png" alt="Bar chart showing 60% increase in revenue">
<!-- Decorative image -->
<img src="decorative-border.png" alt="">
<!-- Image as link -->
<a href="/products">
<img src="logo.png" alt="Acme Products Homepage">
</a>
```
**Icons:**
```html
<!-- Functional icon with text -->
<button>
<svg aria-hidden="true">...</svg>
<span>Save</span>
</button>
<!-- Functional icon without visible text -->
<button aria-label="Save document">
<svg aria-hidden="true">...</svg>
</button>
<!-- Decorative icon -->
<span aria-hidden="true"></span>
```
### ARIA Labels
**Form inputs:**
```html
<!-- Visible label (preferred) -->
<label for="email">Email Address</label>
<input id="email" type="email">
<!-- ARIA label when visible label not possible -->
<input type="search" aria-label="Search products" placeholder="Search...">
```
**Buttons:**
```html
<!-- Text describes action (no ARIA needed) -->
<button>Submit Application</button>
<!-- Icon-only button needs label -->
<button aria-label="Close dialog">
<svg>...</svg>
</button>
```
**Navigation landmarks:**
```html
<nav aria-label="Primary navigation">...</nav>
<nav aria-label="Footer navigation">...</nav>
```
### Live Regions
**Dynamic content updates:**
```html
<!-- Polite: announce when user is idle -->
<div aria-live="polite" aria-atomic="true">
<p>5 new messages</p>
</div>
<!-- Assertive: announce immediately -->
<div role="alert" aria-live="assertive">
<p>Error: Failed to save changes</p>
</div>
<!-- Status: for status messages -->
<div role="status" aria-live="polite">
<p>Saving...</p>
</div>
```
**Loading states:**
```html
<button aria-busy="true" aria-label="Loading, please wait">
<span class="spinner" aria-hidden="true"></span>
Loading...
</button>
```
## Forms
### Labels & Instructions
**Every input needs a label:**
```html
<!-- Explicit label (preferred) -->
<label for="username">Username</label>
<input id="username" type="text">
<!-- Implicit label -->
<label>
Username
<input type="text">
</label>
```
**Required fields:**
```html
<label for="email">
Email Address
<span aria-label="required">*</span>
</label>
<input id="email" type="email" required aria-required="true">
```
**Helper text:**
```html
<label for="password">Password</label>
<input id="password"
type="password"
aria-describedby="password-hint">
<div id="password-hint">
Must be at least 8 characters
</div>
```
### Error Handling
**Validation errors:**
```html
<label for="email">Email Address</label>
<input id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error">
<div id="email-error" role="alert">
Please enter a valid email address
</div>
```
**Error summary:**
```html
<div role="alert" aria-labelledby="error-heading">
<h2 id="error-heading">There are 2 errors in this form</h2>
<ul>
<li><a href="#email">Email address is required</a></li>
<li><a href="#password">Password must be at least 8 characters</a></li>
</ul>
</div>
```
### Fieldsets & Groups
**Related inputs:**
```html
<fieldset>
<legend>Shipping Address</legend>
<label for="street">Street</label>
<input id="street" type="text">
<label for="city">City</label>
<input id="city" type="text">
</fieldset>
```
**Radio button groups:**
```html
<fieldset>
<legend>Select your plan</legend>
<label>
<input type="radio" name="plan" value="basic">
Basic
</label>
<label>
<input type="radio" name="plan" value="pro">
Pro
</label>
</fieldset>
```
## Interactive Components
### Buttons
**Button requirements:**
- Minimum size: 44x44px (iOS guideline)
- Clear focus indicator
- Disabled state clearly visible
- Loading state announced to screen readers
```html
<!-- Primary action -->
<button type="button">Save Changes</button>
<!-- Disabled -->
<button type="button" disabled aria-disabled="true">
Save Changes
</button>
<!-- Loading -->
<button type="button" aria-busy="true" aria-label="Saving, please wait">
<span class="spinner" aria-hidden="true"></span>
Saving...
</button>
```
### Modals/Dialogs
**Modal requirements:**
- Focus trap (keep focus inside modal)
- Close with Escape key
- Return focus to trigger element on close
- Screen readers announce modal opening
```html
<div role="dialog"
aria-modal="true"
aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Action</h2>
<p>Are you sure you want to delete this item?</p>
<button type="button">Cancel</button>
<button type="button">Delete</button>
</div>
```
### Dropdowns/Menus
**Menu button pattern:**
```html
<button aria-haspopup="true"
aria-expanded="false"
aria-controls="menu">
Options
</button>
<ul id="menu" role="menu">
<li role="menuitem">Edit</li>
<li role="menuitem">Delete</li>
</ul>
```
### Tabs
**Tab pattern:**
```html
<div role="tablist" aria-label="Project details">
<button role="tab"
aria-selected="true"
aria-controls="overview-panel">
Overview
</button>
<button role="tab"
aria-selected="false"
aria-controls="activity-panel">
Activity
</button>
</div>
<div id="overview-panel" role="tabpanel">
Overview content...
</div>
<div id="activity-panel" role="tabpanel" hidden>
Activity content...
</div>
```
## Mobile & Touch
### Touch Targets
**Minimum sizes:**
- 44x44px on iOS (Apple guideline)
- 48x48px on Android (Material Design)
- Use larger targets for primary actions
**Spacing:**
- 8px minimum between touch targets
- More spacing for dense interfaces
### Viewport & Zoom
**Allow zoom:**
```html
<!-- Good -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bad - don't prevent zoom -->
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
```
**Responsive text:**
- Use relative units (rem, em)
- Don't set maximum font size
- Ensure text reflows at 200% zoom
## Testing Checklist
### Automated Testing
- [ ] Run Lighthouse accessibility audit
- [ ] Check WAVE browser extension
- [ ] Validate HTML (W3C validator)
- [ ] Test color contrast (WebAIM checker)
### Manual Testing
- [ ] Navigate entire site using only keyboard
- [ ] Test with screen reader (NVDA, JAWS, VoiceOver)
- [ ] Zoom to 200% and verify layout
- [ ] Test with browser extensions disabled
- [ ] Test on mobile device
- [ ] Test with reduced motion settings
- [ ] Test in high contrast mode
### Specific Checks
- [ ] All images have alt text
- [ ] Forms have proper labels
- [ ] Focus indicators are visible
- [ ] Color is not only method of conveying info
- [ ] Text has sufficient contrast
- [ ] Headings are properly nested
- [ ] Links have descriptive text
- [ ] Videos have captions
- [ ] Audio has transcripts
- [ ] Tables have proper headers
- [ ] Interactive elements are keyboard accessible
- [ ] Error messages are clear and helpful
- [ ] Loading states are announced
- [ ] Modals trap focus and close with Escape
## Common Mistakes
**Don't:**
- Use `<div>` or `<span>` as buttons (use `<button>`)
- Remove focus indicators without replacements
- Use color alone to convey meaning
- Disable zoom on mobile
- Skip heading levels
- Use placeholder as label
- Make click targets too small
- Forget alt text on images
- Use ambiguous link text ("click here")
- Prevent keyboard access to functionality
**Do:**
- Use semantic HTML elements
- Provide clear focus indicators
- Label all form inputs
- Make touch targets 44x44px minimum
- Test with keyboard and screen reader
- Provide alternatives for non-text content
- Write descriptive link text
- Announce dynamic content changes
- Support keyboard navigation patterns
- Document accessibility features
## Resources
**Testing tools:**
- Chrome DevTools Lighthouse
- WAVE (Web Accessibility Evaluation Tool)
- axe DevTools
- Screen readers: NVDA (Windows), JAWS (Windows), VoiceOver (Mac/iOS)
**Guidelines:**
- WCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/
- MDN Accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility
- WebAIM: https://webaim.org/
**Patterns:**
- ARIA Authoring Practices: https://www.w3.org/WAI/ARIA/apg/
- Inclusive Components: https://inclusive-components.design/