Skip to main content

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);
}

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):

<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>
<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:

// 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​

  1. Semantic HTML: Use <nav>, <main>, <section>, <article>, <aside>
  2. Proper headings: H1 β†’ H2 β†’ H3 hierarchy
  3. ARIA labels: For all interactive elements
  4. Skip links: Jump to main content
  5. Focus management: Trap focus in modals

CSS Requirements​

  1. Focus indicators: 3px outline, 2px offset
  2. Contrast ratios: Verify all text β‰₯4.5:1
  3. Responsive text: Use rem/em units
  4. Reduced motion: Respect prefers-reduced-motion

JavaScript Requirements​

  1. Keyboard handlers: Arrow keys, Esc, Enter
  2. Live regions: Announce dynamic updates
  3. Focus management: Move focus to modals when opened
  4. Error handling: Clear error messages with suggestions

Testing​

  1. Automated: axe DevTools, Lighthouse (95+ score)
  2. Manual: NVDA, JAWS, VoiceOver
  3. Keyboard: 100% navigable without mouse
  4. Zoom: 200% zoom without horizontal scroll

Next: 14. Technical Implementation Previous: 12. Responsive Design Index: Master Index