mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 13:51:09 -05:00
Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols. - Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage. - Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
350
docs/accessibility/component-patterns.md
Normal file
350
docs/accessibility/component-patterns.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# Accessible Component Patterns
|
||||
|
||||
This document provides code examples for creating accessible components in ThrillWiki. Follow these patterns to ensure WCAG 2.1 AA compliance.
|
||||
|
||||
## Button
|
||||
|
||||
### Standard Button
|
||||
```django
|
||||
{% include 'components/ui/button.html' with
|
||||
text='Save Changes'
|
||||
variant='primary'
|
||||
type='submit'
|
||||
%}
|
||||
```
|
||||
|
||||
### Icon Button (REQUIRED: aria_label)
|
||||
```django
|
||||
{% include 'components/ui/button.html' with
|
||||
icon=close_svg
|
||||
size='icon'
|
||||
aria_label='Close dialog'
|
||||
variant='ghost'
|
||||
%}
|
||||
```
|
||||
|
||||
**Important**: Icon-only buttons MUST include `aria_label` for screen reader accessibility.
|
||||
|
||||
### Disabled Button
|
||||
```django
|
||||
{% include 'components/ui/button.html' with
|
||||
text='Submit'
|
||||
disabled=True
|
||||
%}
|
||||
```
|
||||
|
||||
### Button with HTMX
|
||||
```django
|
||||
{% include 'components/ui/button.html' with
|
||||
text='Load More'
|
||||
hx_get='/api/items?page=2'
|
||||
hx_target='#item-list'
|
||||
hx_swap='beforeend'
|
||||
%}
|
||||
```
|
||||
|
||||
## Form Field
|
||||
|
||||
### Standard Field
|
||||
```django
|
||||
{% include 'forms/partials/form_field.html' with
|
||||
field=form.email
|
||||
%}
|
||||
```
|
||||
|
||||
The form_field.html component automatically handles:
|
||||
- Label association via `for` attribute
|
||||
- Error message display with proper ARIA
|
||||
- Required field indication
|
||||
- Help text with `aria-describedby`
|
||||
|
||||
### Field with Help Text
|
||||
```django
|
||||
{% include 'forms/partials/form_field.html' with
|
||||
field=form.password
|
||||
help_text='Must be at least 8 characters'
|
||||
%}
|
||||
```
|
||||
|
||||
### Field with HTMX Validation
|
||||
```django
|
||||
{% include 'forms/partials/form_field.html' with
|
||||
field=form.username
|
||||
hx_validate=True
|
||||
hx_validate_url='/api/validate-username/'
|
||||
%}
|
||||
```
|
||||
|
||||
### Complete Form Example
|
||||
```django
|
||||
<form method="post" role="form" aria-label="User registration form">
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr-only">Account Information</legend>
|
||||
{% include 'forms/partials/form_field.html' with field=form.username %}
|
||||
{% include 'forms/partials/form_field.html' with field=form.email %}
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr-only">Security</legend>
|
||||
{% include 'forms/partials/form_field.html' with field=form.password1 %}
|
||||
{% include 'forms/partials/form_field.html' with field=form.password2 %}
|
||||
</fieldset>
|
||||
|
||||
{% include 'components/ui/button.html' with
|
||||
text='Create Account'
|
||||
type='submit'
|
||||
variant='primary'
|
||||
%}
|
||||
</form>
|
||||
```
|
||||
|
||||
## Modal
|
||||
|
||||
### Basic Modal
|
||||
```django
|
||||
{% extends 'components/modals/modal_base.html' %}
|
||||
|
||||
{% block modal_body %}
|
||||
<p>Are you sure you want to delete this item?</p>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### Modal with Actions
|
||||
```django
|
||||
{% extends 'components/modals/modal_base.html' %}
|
||||
|
||||
{% block modal_body %}
|
||||
<p>This action cannot be undone.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_footer %}
|
||||
<button @click="{{ show_var }} = false" type="button">
|
||||
Cancel
|
||||
</button>
|
||||
{% include 'components/ui/button.html' with
|
||||
text='Delete'
|
||||
variant='destructive'
|
||||
x_on_click='confirmDelete()'
|
||||
%}
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
The modal_inner.html component automatically provides:
|
||||
- `role="dialog"` and `aria-modal="true"`
|
||||
- `aria-labelledby` pointing to title
|
||||
- `aria-describedby` pointing to body (and subtitle if present)
|
||||
- Focus trap with Tab/Shift+Tab cycling
|
||||
- Home/End key support for first/last focusable element
|
||||
- Escape key to close (configurable)
|
||||
- Auto-focus on first focusable element
|
||||
|
||||
## Navigation Menu
|
||||
|
||||
### Dropdown Menu
|
||||
```django
|
||||
<div x-data="{ open: false }" @click.outside="open = false" @keydown.escape="open = false">
|
||||
<button
|
||||
@click="open = !open"
|
||||
aria-haspopup="true"
|
||||
:aria-expanded="open.toString()"
|
||||
aria-label="User menu">
|
||||
<span>Menu</span>
|
||||
<i class="fas fa-chevron-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="open"
|
||||
x-transition
|
||||
role="menu"
|
||||
aria-label="User account options"
|
||||
class="dropdown-menu">
|
||||
|
||||
<a role="menuitem" href="/profile" class="menu-item">Profile</a>
|
||||
<a role="menuitem" href="/settings" class="menu-item">Settings</a>
|
||||
<div role="separator" class="border-t"></div>
|
||||
<button role="menuitem" type="submit" class="menu-item">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Search
|
||||
|
||||
### Accessible Search with Results
|
||||
```django
|
||||
<div role="search">
|
||||
<label for="search-input" class="sr-only">Search parks and rides</label>
|
||||
<input
|
||||
id="search-input"
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
hx-get="/search"
|
||||
hx-target="#search-results"
|
||||
hx-trigger="input changed delay:300ms"
|
||||
autocomplete="off"
|
||||
aria-describedby="search-status"
|
||||
aria-controls="search-results"
|
||||
/>
|
||||
|
||||
<div
|
||||
id="search-results"
|
||||
role="listbox"
|
||||
aria-label="Search results"
|
||||
aria-live="polite">
|
||||
<!-- Results populated by HTMX -->
|
||||
</div>
|
||||
|
||||
<div id="search-status" class="sr-only" aria-live="polite" aria-atomic="true">
|
||||
<!-- Status announcements -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Search Result Item
|
||||
```django
|
||||
<div role="option" aria-selected="false" class="search-result">
|
||||
<a href="{{ result.url }}">
|
||||
<span>{{ result.name }}</span>
|
||||
<span class="text-muted-foreground">{{ result.type }}</span>
|
||||
</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Live Regions
|
||||
|
||||
### Status Announcements
|
||||
```django
|
||||
<div role="status" aria-live="polite" aria-atomic="true" class="sr-only">
|
||||
{{ status_message }}
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `aria-live="polite"` for non-urgent updates that can wait for user to finish current action.
|
||||
|
||||
### Alert Messages
|
||||
```django
|
||||
<div role="alert" aria-live="assertive">
|
||||
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
|
||||
{{ error_message }}
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `aria-live="assertive"` for critical errors that require immediate attention.
|
||||
|
||||
### Loading State
|
||||
```django
|
||||
<div role="status" aria-live="polite" aria-busy="true">
|
||||
<span class="sr-only">Loading results...</span>
|
||||
<div class="htmx-indicator" aria-hidden="true">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Images
|
||||
|
||||
### Meaningful Image
|
||||
```django
|
||||
<img
|
||||
src="{{ park.image.url }}"
|
||||
alt="{{ park.name }} - {{ park.location }}"
|
||||
/>
|
||||
```
|
||||
|
||||
### Decorative Image
|
||||
```django
|
||||
<img src="/decorative-pattern.svg" alt="" role="presentation" />
|
||||
```
|
||||
|
||||
### Avatar with Name
|
||||
```django
|
||||
{% if user.profile.avatar %}
|
||||
<img
|
||||
src="{{ user.profile.avatar.url }}"
|
||||
alt="{{ user.get_full_name|default:user.username }}'s profile picture"
|
||||
class="avatar"
|
||||
/>
|
||||
{% else %}
|
||||
<div class="avatar-placeholder" aria-hidden="true">
|
||||
{{ user.username.0|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
```django
|
||||
{% include 'components/navigation/breadcrumbs.html' %}
|
||||
```
|
||||
|
||||
The breadcrumbs component automatically provides:
|
||||
- `<nav>` element with `aria-label="Breadcrumb"`
|
||||
- Ordered list for semantic structure
|
||||
- `aria-current="page"` on current page
|
||||
- Hidden separators with `aria-hidden="true"`
|
||||
- Schema.org JSON-LD structured data
|
||||
|
||||
## Skip Link
|
||||
|
||||
Add to the top of your base template:
|
||||
```django
|
||||
<a href="#main-content" class="skip-link sr-only-focusable">
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
<!-- ... header and navigation ... -->
|
||||
|
||||
<main id="main-content" tabindex="-1">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
```
|
||||
|
||||
## Theme Toggle
|
||||
|
||||
```django
|
||||
<button
|
||||
@click="toggleTheme()"
|
||||
aria-label="Toggle theme"
|
||||
:aria-pressed="isDarkMode.toString()"
|
||||
class="theme-toggle">
|
||||
<i class="fas fa-sun" aria-hidden="true"></i>
|
||||
<i class="fas fa-moon" aria-hidden="true"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
## Focus Management Utilities
|
||||
|
||||
### Focus Trap (Alpine.js)
|
||||
```javascript
|
||||
// Already implemented in modal_inner.html
|
||||
@keydown.tab.prevent="trapFocus($event)"
|
||||
```
|
||||
|
||||
### Return Focus After Action
|
||||
```javascript
|
||||
// Store the trigger element
|
||||
const trigger = document.activeElement;
|
||||
|
||||
// After modal closes
|
||||
trigger.focus();
|
||||
```
|
||||
|
||||
## Testing Your Components
|
||||
|
||||
### Quick Accessibility Audit
|
||||
1. Can you Tab through all interactive elements?
|
||||
2. Is focus indicator visible?
|
||||
3. Does Enter/Space activate buttons?
|
||||
4. Does Escape close modals/dropdowns?
|
||||
5. Does screen reader announce all content?
|
||||
6. Is color not the only indicator of state?
|
||||
|
||||
### Automated Testing
|
||||
```bash
|
||||
# Run accessibility tests
|
||||
python manage.py test backend.tests.accessibility
|
||||
|
||||
# Use axe browser extension for quick audits
|
||||
# Install from: https://www.deque.com/axe/
|
||||
```
|
||||
138
docs/accessibility/keyboard-navigation.md
Normal file
138
docs/accessibility/keyboard-navigation.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Keyboard Navigation Guide
|
||||
|
||||
ThrillWiki is designed to be fully accessible via keyboard. This guide documents all keyboard shortcuts and navigation patterns.
|
||||
|
||||
## Global Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Move focus forward |
|
||||
| Shift + Tab | Move focus backward |
|
||||
| Enter | Activate focused element |
|
||||
| Space | Activate buttons/checkboxes |
|
||||
| Escape | Close modals/dropdowns |
|
||||
|
||||
## Navigation Menu
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Focus menu button |
|
||||
| Enter/Space | Open menu |
|
||||
| Arrow Down | Next menu item |
|
||||
| Arrow Up | Previous menu item |
|
||||
| Escape | Close menu |
|
||||
| Home | First menu item |
|
||||
| End | Last menu item |
|
||||
|
||||
## Search
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Focus search input |
|
||||
| Type | Start searching (debounced 300ms) |
|
||||
| Arrow Down | Navigate to next result |
|
||||
| Arrow Up | Navigate to previous result (or back to input) |
|
||||
| Enter | Select current result (navigate to link) |
|
||||
| Escape | Close results and blur input |
|
||||
| Home | Jump to first result |
|
||||
| End | Jump to last result |
|
||||
|
||||
## Modals
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Next focusable element (trapped within modal) |
|
||||
| Shift + Tab | Previous focusable element (trapped within modal) |
|
||||
| Escape | Close modal |
|
||||
| Home | Jump to first focusable element |
|
||||
| End | Jump to last focusable element |
|
||||
|
||||
## Forms
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Move to next field |
|
||||
| Shift + Tab | Move to previous field |
|
||||
| Enter | Submit form (when focused on submit button) |
|
||||
| Space | Toggle checkbox/radio button |
|
||||
| Arrow Down/Up | Navigate select options |
|
||||
| Escape | Close select dropdown |
|
||||
|
||||
## Dropdowns (User Menu, Browse Menu)
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Enter/Space | Toggle dropdown open/closed |
|
||||
| Escape | Close dropdown |
|
||||
| Arrow Down | Navigate to next item |
|
||||
| Arrow Up | Navigate to previous item |
|
||||
| Tab | Move through items (or exit dropdown) |
|
||||
|
||||
## Mobile Menu
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Enter/Space | Open mobile menu |
|
||||
| Escape | Close mobile menu |
|
||||
| Tab | Navigate through menu items |
|
||||
|
||||
## Theme Toggle
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Enter/Space | Toggle between light and dark mode |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Focus Management
|
||||
- All interactive elements must be reachable via keyboard
|
||||
- Focus order follows logical reading order (top to bottom, left to right)
|
||||
- Focus is trapped within modals when open
|
||||
- Focus returns to trigger element when modal/dropdown closes
|
||||
|
||||
### Focus Indicators
|
||||
- All focusable elements have visible focus indicators
|
||||
- Focus ring: 2px solid primary color with 2px offset
|
||||
- Enhanced visibility in high contrast mode
|
||||
- Custom focus styles for complex components
|
||||
|
||||
### ARIA Attributes
|
||||
- `aria-expanded`: Indicates dropdown/menu open state
|
||||
- `aria-haspopup`: Indicates element triggers a popup
|
||||
- `aria-label`: Provides accessible name for icon-only buttons
|
||||
- `aria-modal`: Indicates modal dialog
|
||||
- `aria-controls`: Links trigger to controlled element
|
||||
- `aria-describedby`: Links element to description
|
||||
- `aria-live`: Announces dynamic content changes
|
||||
|
||||
### Skip Links
|
||||
- Skip to main content link available (hidden until focused)
|
||||
- Appears at top of page when Tab is pressed first
|
||||
|
||||
## Testing Keyboard Navigation
|
||||
|
||||
### Manual Testing Checklist
|
||||
1. Navigate entire site using only keyboard
|
||||
2. Verify all interactive elements are reachable with Tab
|
||||
3. Verify Enter/Space activates all buttons and links
|
||||
4. Verify Escape closes all modals and dropdowns
|
||||
5. Verify focus indicators are visible
|
||||
6. Verify focus order is logical
|
||||
7. Verify focus is trapped in modals
|
||||
8. Verify focus returns to trigger after closing modals
|
||||
|
||||
### Automated Testing
|
||||
Run accessibility tests:
|
||||
```bash
|
||||
python manage.py test backend.tests.accessibility
|
||||
```
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Keyboard navigation is tested on:
|
||||
- Chrome (latest)
|
||||
- Firefox (latest)
|
||||
- Safari (latest)
|
||||
- Edge (latest)
|
||||
|
||||
Note: Some keyboard shortcuts may conflict with browser shortcuts. In those cases, browser shortcuts take precedence.
|
||||
200
docs/accessibility/screen-reader-testing.md
Normal file
200
docs/accessibility/screen-reader-testing.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Screen Reader Testing Checklist
|
||||
|
||||
This document provides a comprehensive checklist for testing ThrillWiki with screen readers to ensure WCAG 2.1 AA compliance.
|
||||
|
||||
## Recommended Screen Readers
|
||||
|
||||
| Screen Reader | Platform | Cost | Notes |
|
||||
|--------------|----------|------|-------|
|
||||
| NVDA | Windows | Free | Most widely used free option |
|
||||
| JAWS | Windows | Commercial | Industry standard |
|
||||
| VoiceOver | macOS/iOS | Built-in | Activate with Cmd+F5 |
|
||||
| TalkBack | Android | Built-in | Enable in Accessibility settings |
|
||||
| Narrator | Windows | Built-in | Basic testing |
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Navigation
|
||||
|
||||
- [ ] Skip to main content link works and is announced
|
||||
- [ ] All navigation items are announced with their role
|
||||
- [ ] Current page is indicated (aria-current="page")
|
||||
- [ ] Dropdown menus announce as "button, collapsed" when closed
|
||||
- [ ] Dropdown menus announce as "button, expanded" when open
|
||||
- [ ] Menu items announce with role="menuitem"
|
||||
- [ ] Breadcrumbs are announced as navigation landmark
|
||||
|
||||
### Search
|
||||
|
||||
- [ ] Search input is announced with label "Search parks and rides"
|
||||
- [ ] Search results count is announced when results appear
|
||||
- [ ] Each result is announced with its content
|
||||
- [ ] Arrow key navigation announces current selection
|
||||
- [ ] "No results found" is announced when applicable
|
||||
|
||||
### Forms
|
||||
|
||||
- [ ] All form fields have associated labels announced
|
||||
- [ ] Required fields announce "required"
|
||||
- [ ] Error messages are announced immediately when form validates
|
||||
- [ ] Success messages are announced after form submission
|
||||
- [ ] Help text is associated and announced with fields
|
||||
- [ ] Field types are announced (text input, checkbox, select, etc.)
|
||||
|
||||
### Interactive Components
|
||||
|
||||
- [ ] Buttons announce their purpose/label
|
||||
- [ ] Icon-only buttons announce their aria-label
|
||||
- [ ] Links announce destination or purpose
|
||||
- [ ] Modals announce their title when opened
|
||||
- [ ] Modal content is announced as dialog
|
||||
- [ ] Closing modal announces return to previous context
|
||||
- [ ] Theme toggle announces current state (pressed/not pressed)
|
||||
|
||||
### Dynamic Content
|
||||
|
||||
- [ ] Search results are announced via aria-live region
|
||||
- [ ] Filter changes announce result count
|
||||
- [ ] Status updates (success, error) are announced
|
||||
- [ ] Loading states are announced ("Loading...")
|
||||
- [ ] HTMX content swaps announce via live regions
|
||||
|
||||
### Images
|
||||
|
||||
- [ ] All meaningful images have descriptive alt text
|
||||
- [ ] Decorative images are hidden (alt="" or aria-hidden)
|
||||
- [ ] Complex images have long descriptions available
|
||||
- [ ] Avatar images include user's name in alt text
|
||||
|
||||
### Tables (if applicable)
|
||||
|
||||
- [ ] Tables have proper headers (th elements)
|
||||
- [ ] Table caption or aria-label describes table purpose
|
||||
- [ ] Data cells associate with headers
|
||||
|
||||
### Headings
|
||||
|
||||
- [ ] Page has exactly one h1
|
||||
- [ ] Heading hierarchy is logical (h1 > h2 > h3)
|
||||
- [ ] No skipped heading levels
|
||||
- [ ] Headings describe section content
|
||||
|
||||
## Testing Commands
|
||||
|
||||
### NVDA (Windows)
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| Insert + Down Arrow | Read next item |
|
||||
| Insert + Up Arrow | Read previous item |
|
||||
| Insert + Space | Read current item |
|
||||
| Insert + F7 | Elements list (links, headings, landmarks) |
|
||||
| H | Next heading |
|
||||
| Shift + H | Previous heading |
|
||||
| K | Next link |
|
||||
| F | Next form field |
|
||||
| D | Next landmark |
|
||||
| Insert + Ctrl + N | Read notifications |
|
||||
|
||||
### VoiceOver (macOS)
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| VO + Right Arrow | Next item |
|
||||
| VO + Left Arrow | Previous item |
|
||||
| VO + U | Rotor (elements list) |
|
||||
| VO + A | Read all from current position |
|
||||
| VO + Cmd + H | Next heading |
|
||||
| VO + Cmd + J | Next form control |
|
||||
| VO + Cmd + L | Next link |
|
||||
|
||||
Note: VO = Control + Option
|
||||
|
||||
### VoiceOver (iOS)
|
||||
|
||||
| Gesture | Action |
|
||||
|---------|--------|
|
||||
| Swipe Right | Next item |
|
||||
| Swipe Left | Previous item |
|
||||
| Double Tap | Activate item |
|
||||
| Two-finger Swipe Up | Read all from current position |
|
||||
| Rotor (two-finger twist) | Change navigation mode |
|
||||
|
||||
### JAWS (Windows)
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| Down Arrow | Next item |
|
||||
| Up Arrow | Previous item |
|
||||
| Insert + F5 | Forms list |
|
||||
| Insert + F6 | Headings list |
|
||||
| Insert + F7 | Links list |
|
||||
| H | Next heading |
|
||||
| F | Next form field |
|
||||
| T | Next table |
|
||||
| R | Next region/landmark |
|
||||
|
||||
### TalkBack (Android)
|
||||
|
||||
| Gesture | Action |
|
||||
|---------|--------|
|
||||
| Swipe Right | Next item |
|
||||
| Swipe Left | Previous item |
|
||||
| Double Tap | Activate item |
|
||||
| Swipe Up then Down | Navigation settings |
|
||||
|
||||
## Common Issues to Watch For
|
||||
|
||||
### Problematic Patterns
|
||||
1. **Missing labels**: Form fields without associated labels
|
||||
2. **Duplicate IDs**: Multiple elements with same ID breaks aria-describedby
|
||||
3. **Empty buttons**: Buttons with no text or aria-label
|
||||
4. **Inaccessible modals**: Focus not trapped, no escape to close
|
||||
5. **Auto-playing media**: Audio/video that plays automatically
|
||||
6. **Timeout without warning**: Sessions expiring without notice
|
||||
7. **Moving focus unexpectedly**: Focus jumping after interactions
|
||||
8. **Color-only information**: Status conveyed only by color
|
||||
|
||||
### Good Patterns
|
||||
1. **Clear labels**: Every form field has descriptive label
|
||||
2. **Error prevention**: Clear instructions, validation before submit
|
||||
3. **Focus management**: Logical order, visible indicators, trapped in modals
|
||||
4. **Consistent navigation**: Same navigation pattern on all pages
|
||||
5. **Multiple ways**: Multiple paths to same content
|
||||
6. **Descriptive links**: Link text describes destination (not "click here")
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
When reporting accessibility issues, include:
|
||||
1. Screen reader and version used
|
||||
2. Browser and version
|
||||
3. Page URL
|
||||
4. Steps to reproduce
|
||||
5. Expected behavior
|
||||
6. Actual behavior (what was announced)
|
||||
7. WCAG success criterion violated
|
||||
|
||||
## WCAG 2.1 AA Quick Reference
|
||||
|
||||
### Level A (Must Have)
|
||||
- 1.1.1 Non-text Content (alt text)
|
||||
- 1.3.1 Info and Relationships (semantic HTML)
|
||||
- 2.1.1 Keyboard (all functionality via keyboard)
|
||||
- 2.4.1 Bypass Blocks (skip links)
|
||||
- 4.1.2 Name, Role, Value (ARIA)
|
||||
|
||||
### Level AA (Should Have)
|
||||
- 1.4.3 Contrast (Minimum) (4.5:1 ratio)
|
||||
- 1.4.4 Resize Text (200% zoom)
|
||||
- 2.4.6 Headings and Labels (descriptive)
|
||||
- 2.4.7 Focus Visible (visible focus indicator)
|
||||
- 3.2.3 Consistent Navigation
|
||||
- 3.2.4 Consistent Identification
|
||||
|
||||
## Resources
|
||||
|
||||
- [WCAG 2.1 Guidelines](https://www.w3.org/TR/WCAG21/)
|
||||
- [WebAIM Screen Reader Survey](https://webaim.org/projects/screenreadersurvey9/)
|
||||
- [NVDA User Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html)
|
||||
- [VoiceOver User Guide](https://support.apple.com/guide/voiceover/welcome/mac)
|
||||
- [JAWS Quick Start](https://www.freedomscientific.com/products/software/jaws/)
|
||||
Reference in New Issue
Block a user