13. Accessibility (WCAG 2.1 AA)
Overviewβ
Dashboard 2.0 meets WCAG 2.1 Level AA accessibility standards to ensure usability for all users, including those with visual, motor, cognitive, and hearing disabilities. Accessibility is not an afterthoughtβit's integrated into every component from the ground up.
Compliance Target: WCAG 2.1 AA Testing Tools: axe DevTools, NVDA, VoiceOver, JAWS Validation: Automated + manual testing across all major screen readers
13.1 Keyboard Navigationβ
Full Keyboard Accessβ
All functionality must be accessible via keyboard alone (WCAG 2.1.1).
Standard Keyboard Shortcuts:
- Tab: Move focus to next interactive element
- Shift+Tab: Move focus to previous element
- Enter/Space: Activate buttons and links
- Arrow keys: Navigate within components (dropdowns, Kanban boards)
- Esc: Close modals and dropdowns
- Cmd/Ctrl+K: Open global search
Tab Orderβ
Logical tab order following visual flow (topβbottom, leftβright):
<!-- Example: Proper tab order for Kanban board -->
<div class="kanban-board">
<button class="add-column" tabindex="0">Add Column</button>
<div class="kanban-column" tabindex="0">
<h3>To Do</h3>
<div class="task-card" tabindex="0">Task 1</div>
<div class="task-card" tabindex="0">Task 2</div>
</div>
<div class="kanban-column" tabindex="0">
<h3>In Progress</h3>
<div class="task-card" tabindex="0">Task 3</div>
</div>
</div>
Focus Indicatorsβ
Visible focus indicators for all interactive elements (WCAG 2.4.7):
/* Global focus style */
*:focus {
outline: 3px solid #0066CC;
outline-offset: 2px;
}
/* Custom focus for buttons */
button:focus {
outline: 3px solid #0066CC;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.2);
}
/* Skip focus for mouse clicks (only keyboard) */
button:focus:not(:focus-visible) {
outline: none;
box-shadow: none;
}
button:focus-visible {
outline: 3px solid #0066CC;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.2);
}
Skip Linksβ
Allow keyboard users to skip navigation (WCAG 2.4.1):
<a href="#main-content" class="skip-link">Skip to main content</a>
<a href="#navigation" class="skip-link">Skip to navigation</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px 16px;
background: #0066CC;
color: #FFFFFF;
z-index: 10000;
}
.skip-link:focus {
top: 0;
}
</style>
13.2 Screen Reader Supportβ
ARIA Labels and Rolesβ
Proper ARIA attributes for all interactive components (WCAG 4.1.2):
Navigationβ
<nav aria-label="Main navigation">
<ul role="list">
<li role="listitem">
<a href="/dashboard" aria-current="page">Dashboard</a>
</li>
<li role="listitem">
<a href="/projects">Projects</a>
</li>
</ul>
</nav>
Buttonsβ
<button aria-label="Close modal" aria-pressed="false">
<span aria-hidden="true">β</span>
</button>
<button aria-label="Filter tasks" aria-expanded="false" aria-controls="filter-modal">
Filter
</button>
Searchβ
<div role="search">
<input
type="text"
aria-label="Search tasks, projects, checkpoints"
aria-autocomplete="list"
aria-controls="search-results"
aria-expanded="false"
/>
<div id="search-results" role="listbox" aria-label="Search results">
<div role="option" aria-selected="false">Result 1</div>
</div>
</div>
Kanban Boardβ
<div class="kanban-board" role="region" aria-label="Task board">
<div class="kanban-column" role="list" aria-label="To Do tasks">
<div
class="task-card"
role="listitem"
tabindex="0"
aria-label="Implement user authentication, Priority: P0, Status: Pending"
>
<!-- Task content -->
</div>
</div>
</div>
Modalsβ
<div
class="modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<h2 id="modal-title">Task Details</h2>
<p id="modal-description">View and edit task information</p>
<!-- Modal content -->
</div>
Live Regionsβ
Announce dynamic updates to screen readers (WCAG 4.1.3):
<!-- Status messages -->
<div aria-live="polite" aria-atomic="true" class="sr-only">
<span id="status-message">Task moved to In Progress</span>
</div>
<!-- Error messages -->
<div aria-live="assertive" aria-atomic="true" class="sr-only">
<span id="error-message">Failed to save task</span>
</div>
// Announce status changes
function announceStatus(message) {
const statusEl = document.getElementById('status-message');
statusEl.textContent = message;
}
// Announce errors
function announceError(message) {
const errorEl = document.getElementById('error-message');
errorEl.textContent = message;
}
Screen Reader Only Textβ
Provide context for screen reader users:
<style>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>
<!-- Example: Icon-only button -->
<button aria-label="Delete task">
<span class="sr-only">Delete task</span>
<span aria-hidden="true">ποΈ</span>
</button>
13.3 Color Contrastβ
WCAG AA Contrast Ratiosβ
Minimum contrast ratios (WCAG 1.4.3):
- Normal text: 4.5:1 minimum
- Large text (18px+ or 14px+ bold): 3:1 minimum
- UI components and graphical objects: 3:1 minimum
Color Palette with Verified Ratiosβ
/* Primary Colors */
--color-primary: #0066CC; /* Blue */
--color-text-dark: #111827; /* Nearly black */
--color-text-medium: #6B7280; /* Medium gray */
--color-text-light: #9CA3AF; /* Light gray */
--color-background: #FFFFFF; /* White */
/* Contrast Ratios */
/* #111827 on #FFFFFF = 16.1:1 β
(Exceeds AAA) */
/* #6B7280 on #FFFFFF = 4.6:1 β
(Passes AA) */
/* #9CA3AF on #FFFFFF = 2.8:1 β (Fails AA - use only for decorative) */
/* #0066CC on #FFFFFF = 7.5:1 β
(Exceeds AAA) */
/* Status Colors */
--status-success: #10B981; /* Green - 3.4:1 on white β
*/
--status-warning: #F59E0B; /* Orange - 2.9:1 on white β */
--status-warning-dark: #D97706; /* Dark orange - 4.5:1 on white β
*/
--status-error: #EF4444; /* Red - 4.0:1 on white β
*/
--status-info: #3B82F6; /* Blue - 5.9:1 on white β
*/
/* Backgrounds */
--bg-gray-50: #F9FAFB;
--bg-gray-100: #F3F4F6;
--bg-gray-200: #E5E7EB;
Contrast Testingβ
Manual testing with tools:
- Chrome DevTools: Lighthouse audit
- axe DevTools: Automated contrast check
- WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/
// Automated contrast check example
function checkContrast(foreground, background) {
const contrast = calculateContrastRatio(foreground, background);
if (contrast >= 4.5) {
console.log('β
Passes AA for normal text');
} else if (contrast >= 3.0) {
console.log('β οΈ Passes AA for large text only');
} else {
console.error('β Fails AA - contrast too low');
}
}
Non-Color Indicatorsβ
Never rely on color alone (WCAG 1.4.1):
<!-- Bad: Color-only status -->
<div style="background: red;">Error</div>
<!-- Good: Color + icon + text -->
<div class="status-error">
<span class="icon" aria-hidden="true">β οΈ</span>
<span>Error</span>
</div>
<!-- Good: Color + pattern (for charts) -->
<svg>
<rect fill="#10B981" stroke-dasharray="5,5" /> <!-- Striped green -->
<rect fill="#EF4444" stroke-dasharray="10,2" /> <!-- Dotted red -->
</svg>
13.4 Text Alternativesβ
Images and Iconsβ
Provide alt text for all images (WCAG 1.1.1):
<!-- Informative image -->
<img src="dashboard-chart.png" alt="Task completion chart showing 78% progress" />
<!-- Decorative image -->
<img src="decorative-line.png" alt="" role="presentation" />
<!-- Icon with text -->
<button>
<svg aria-hidden="true"><path d="..." /></svg>
Save
</button>
<!-- Icon-only button -->
<button aria-label="Save task">
<svg aria-hidden="true"><path d="..." /></svg>
</button>
Complex Graphicsβ
Provide detailed descriptions for charts and diagrams:
<figure role="group" aria-labelledby="chart-title" aria-describedby="chart-description">
<h3 id="chart-title">Task Completion Rate</h3>
<p id="chart-description" class="sr-only">
Bar chart showing task completion rate over 8 weeks.
Week 1: 45%, Week 2: 52%, Week 3: 61%, Week 4: 68%,
Week 5: 72%, Week 6: 75%, Week 7: 77%, Week 8: 78%.
Overall trend is steadily increasing.
</p>
<canvas id="completion-chart"></canvas>
<!-- Optional: Data table alternative -->
<details>
<summary>View data table</summary>
<table>
<tr><th>Week</th><th>Completion Rate</th></tr>
<tr><td>Week 1</td><td>45%</td></tr>
<tr><td>Week 2</td><td>52%</td></tr>
<!-- ... -->
</table>
</details>
</figure>
13.5 Forms and Inputsβ
Labels and Instructionsβ
All inputs must have labels (WCAG 1.3.1, 3.3.2):
<!-- Visible label -->
<label for="task-title">Task Title</label>
<input id="task-title" type="text" required aria-required="true" />
<!-- Hidden label (use sparingly) -->
<input type="text" aria-label="Search tasks" />
<!-- Group labels -->
<fieldset>
<legend>Task Priority</legend>
<input type="radio" id="p0" name="priority" value="P0" />
<label for="p0">P0 - Critical</label>
<input type="radio" id="p1" name="priority" value="P1" />
<label for="p1">P1 - High</label>
</fieldset>
Error Identificationβ
Clearly identify errors (WCAG 3.3.1):
<label for="email">Email Address</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
required
/>
<div id="email-error" class="error-message" role="alert">
Please enter a valid email address
</div>
<style>
.error-message {
color: #DC2626; /* Red with 4.5:1 contrast */
font-weight: 600;
margin-top: 4px;
}
input[aria-invalid="true"] {
border-color: #DC2626;
border-width: 2px;
}
</style>
Error Suggestionsβ
Provide helpful error suggestions (WCAG 3.3.3):
<div id="password-error" role="alert">
<strong>Password is too weak.</strong>
<ul>
<li>Use at least 12 characters</li>
<li>Include uppercase and lowercase letters</li>
<li>Include at least one number</li>
</ul>
</div>
13.6 Timing and Animationsβ
No Time Limitsβ
Avoid time limits or make them adjustable (WCAG 2.2.1):
// Bad: Fixed timeout
setTimeout(closeModal, 5000);
// Good: User can extend or disable
let timeout;
function startTimeout() {
timeout = setTimeout(() => {
showTimeoutWarning();
}, 30000); // 30 seconds before warning
}
function showTimeoutWarning() {
const confirmed = confirm('Session about to expire. Extend?');
if (confirmed) {
startTimeout(); // Restart
}
}
Pause Animationsβ
Allow users to pause animations (WCAG 2.2.2, 2.3.3):
<button id="pause-animations" aria-label="Pause animations">
<span id="pause-icon">βΈοΈ</span>
<span id="pause-text">Pause Animations</span>
</button>
<style>
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
Reduced Motionβ
Respect user's motion preferences:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Disable animations
document.body.classList.add('reduce-motion');
}
13.7 Responsive Textβ
Resize Text (WCAG 1.4.4)β
Allow text resize up to 200% without loss of functionality:
/* Use relative units (rem, em) instead of px */
body {
font-size: 16px; /* Base size */
}
h1 {
font-size: 2rem; /* 32px, scales with user preferences */
}
p {
font-size: 1rem; /* 16px, scales with user preferences */
}
/* Avoid fixed widths that break on zoom */
.container {
max-width: 90rem; /* Flexible width */
padding: 2rem;
}
Reflow (WCAG 1.4.10)β
Content reflows without horizontal scrolling at 320px width:
/* Responsive layout */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
/* Avoid fixed widths */
.card {
max-width: 100%; /* Never exceed container */
padding: 1rem;
}
13.8 Testing Checklistβ
Automated Testingβ
Run automated tools (catch 30-40% of issues):
# axe DevTools (Chrome extension)
# Lighthouse (Chrome DevTools)
# WAVE (Browser extension)
# Command-line testing
npm install -g @axe-core/cli
axe http://localhost:8000 --rules wcag2a,wcag2aa
Manual Testingβ
Screen readers:
- NVDA (Windows) - Free, most popular
- JAWS (Windows) - Widely used in enterprise
- VoiceOver (Mac/iOS) - Apple devices
- TalkBack (Android) - Android devices
Keyboard testing:
- Navigate entire dashboard with Tab/Shift+Tab
- All interactive elements reachable
- Focus indicators visible
- Esc closes modals/dropdowns
- Arrow keys work in components
Visual testing:
- Zoom to 200% (Cmd/Ctrl +)
- Test grayscale mode (color blindness simulation)
- Test high contrast mode (Windows)
- Check all text contrast ratios
Responsive testing:
- Test on 320px width (smallest mobile)
- Ensure no horizontal scroll
- Touch targets β₯44px
13.9 WCAG 2.1 AA Compliance Checklistβ
Perceivableβ
- 1.1.1 Non-text content has text alternatives
- 1.3.1 Info and relationships conveyed programmatically
- 1.4.1 Color not used as only means of conveying information
- 1.4.3 Contrast ratio β₯4.5:1 for normal text, β₯3:1 for large text
- 1.4.4 Text resizable to 200% without loss of content
- 1.4.10 Content reflows without horizontal scrolling at 320px
- 1.4.11 Non-text contrast β₯3:1 for UI components
Operableβ
- 2.1.1 All functionality available from keyboard
- 2.1.2 No keyboard trap
- 2.4.1 Skip links provided
- 2.4.3 Focus order is logical
- 2.4.6 Headings and labels are descriptive
- 2.4.7 Focus indicator visible
- 2.5.5 Target size β₯44x44px (mobile)
Understandableβ
- 3.1.1 Language of page identified (lang="en")
- 3.2.1 On focus doesn't trigger unexpected changes
- 3.2.2 On input doesn't trigger unexpected changes
- 3.3.1 Errors identified in text
- 3.3.2 Labels or instructions provided for inputs
- 3.3.3 Error suggestions provided
Robustβ
- 4.1.1 Valid HTML (no parsing errors)
- 4.1.2 Name, role, value for all UI components
- 4.1.3 Status messages programmatically determined
13.10 Implementation Prompt - Accessibilityβ
Create a fully accessible dashboard meeting WCAG 2.1 AA standards.
HTML Structureβ
- Semantic HTML: Use
<nav>,<main>,<section>,<article>,<aside> - Proper headings: H1 β H2 β H3 hierarchy
- ARIA labels: For all interactive elements
- Skip links: Jump to main content
- Focus management: Trap focus in modals
CSS Requirementsβ
- Focus indicators: 3px outline, 2px offset
- Contrast ratios: Verify all text β₯4.5:1
- Responsive text: Use rem/em units
- Reduced motion: Respect prefers-reduced-motion
JavaScript Requirementsβ
- Keyboard handlers: Arrow keys, Esc, Enter
- Live regions: Announce dynamic updates
- Focus management: Move focus to modals when opened
- Error handling: Clear error messages with suggestions
Testingβ
- Automated: axe DevTools, Lighthouse (95+ score)
- Manual: NVDA, JAWS, VoiceOver
- Keyboard: 100% navigable without mouse
- Zoom: 200% zoom without horizontal scroll
Next: 14. Technical Implementation Previous: 12. Responsive Design Index: Master Index