Web Accessibility Guide
WCAG 2.1 Principles (POUR)
| Principle | Description | Key Criteria |
|---|---|---|
| Perceivable | Info must be presentable to users in ways they can perceive | Alt text, captions, color contrast, text resize |
| Operable | UI components and navigation must be operable | Keyboard access, no seizure triggers, enough time |
| Understandable | Information and UI operation must be understandable | Readable text, predictable behavior, error suggestions |
| Robust | Content must be interpreted by assistive technologies | Valid HTML, ARIA, name/role/value |
Color Contrast Requirements
| Level | Normal Text | Large Text (18pt+) |
|---|---|---|
| AA (minimum) | 4.5:1 | 3:1 |
| AAA (enhanced) | 7:1 | 4.5:1 |
ARIA Roles Reference
<!-- Landmark roles -->
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
<main role="main">
<aside role="complementary">
<footer role="contentinfo">
<!-- Interactive roles -->
<div role="button" tabindex="0" aria-pressed="false">Toggle</div>
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Delete</h2>
</div>
<!-- Live regions -->
<div role="alert" aria-live="assertive">Error: Form submission failed</div>
<div aria-live="polite" aria-atomic="true">3 items in cart</div>
<!-- Form accessibility -->
<label for="email">Email address</label>
<input id="email" type="email" aria-required="true" aria-describedby="email-hint">
<span id="email-hint">We'll never share your email</span>
Keyboard Navigation
// Focus management for modal dialogs
function openModal(modal) {
modal.removeAttribute('hidden');
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusable[0];
const lastFocusable = focusable[focusable.length - 1];
firstFocusable.focus();
// Trap focus in modal
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
if (e.key === 'Escape') closeModal(modal);
});
}