Accessibility
Build inclusive interfaces that work for everyone. This guide covers WCAG compliance, keyboard navigation, screen readers, and best practices for accessible design.
Overview
Accessibility ensures that all users, including those with disabilities, can perceive, understand, navigate, and interact with your application. The Everforth Design System is built with WCAG 2.1 AA compliance as a baseline.
Core Principles
Perceivable
Information must be presentable to users in ways they can perceive.
Text Alternatives
Provide text alternatives for non-text content like images, icons, and media.
<!-- Good: Descriptive alt text -->
<img src="chart.png" alt="Sales increased 25% in Q4 2024">
<!-- Good: Decorative image -->
<img src="decorative-line.png" alt="" role="presentation">
<!-- Bad: Missing or unhelpful alt -->
<img src="chart.png" alt="image">Color Contrast
Ensure sufficient contrast between text and background colors.
| Content Type | Minimum Ratio | Everforth Compliance |
|---|---|---|
| Normal text | 4.5:1 | All colors pass |
| Large text (18px+) | 3:1 | All colors pass |
| UI components | 3:1 | All colors pass |
Don't Rely on Color Alone
Use additional indicators like icons, patterns, or text labels.
<!-- Good: Color + icon + text -->
<div class="df-alert df-alert--error">
<svg><!-- error icon --></svg>
<span>Error: Please fix the issues below</span>
</div>
<!-- Bad: Color only -->
<input style="border-color: red;">Operable
Users must be able to operate the interface using various input methods.
Keyboard Navigation
All functionality must be available via keyboard.
| Key | Action |
|---|---|
Tab | Move to next focusable element |
Shift + Tab | Move to previous focusable element |
Enter / Space | Activate buttons, links, checkboxes |
Arrow keys | Navigate within components (tabs, menus) |
Escape | Close modals, dropdowns, tooltips |
Focus Management
Visible focus indicators help keyboard users track their location.
/* All Everforth components include focus styles */
.df-button:focus-visible {
outline: 2px solid var(--df-color-focus-ring);
outline-offset: 2px;
}
/* Never do this */
*:focus { outline: none; } /* Removes focus indicator */Skip Links
Allow users to skip repetitive navigation.
<body>
<a href="#main-content" class="df-skip-link">
Skip to main content
</a>
<nav><!-- Navigation --></nav>
<main id="main-content">
<!-- Page content -->
</main>
</body>Understandable
Content and operation must be understandable.
Clear Labels
Form inputs must have associated labels.
<!-- Good: Explicit label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<!-- Good: Implicit label -->
<label>
Email address
<input type="email" name="email">
</label>
<!-- Bad: Placeholder only -->
<input type="email" placeholder="Email address">Error Handling
Identify errors clearly and provide suggestions.
<div class="df-form-group df-form-group--error">
<label for="password">Password</label>
<input
type="password"
id="password"
aria-describedby="password-error"
aria-invalid="true"
>
<span id="password-error" class="df-form-error">
Password must be at least 8 characters with one number
</span>
</div>Robust
Content must be robust enough to work with assistive technologies.
Semantic HTML
Use the correct HTML elements for their intended purpose.
<!-- Good: Semantic elements -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<main>
<article>
<h1>Article Title</h1>
<p>Content...</p>
</article>
</main>
<!-- Bad: Div soup -->
<div class="nav">
<div class="nav-item" onclick="...">Home</div>
</div>ARIA Guidelines
Use ARIA (Accessible Rich Internet Applications) to enhance accessibility when HTML semantics aren't sufficient.
ARIA Roles
<!-- Landmark roles (prefer semantic HTML) -->
<div role="navigation">...</div> <!-- Use <nav> instead -->
<div role="main">...</div> <!-- Use <main> instead -->
<!-- Widget roles -->
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
<div role="tabpanel">Content</div>
<!-- Live regions -->
<div role="alert">Error message</div>
<div role="status" aria-live="polite">Loading...</div>ARIA States and Properties
<!-- Expanded/collapsed -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>
<!-- Selected -->
<li role="option" aria-selected="true">Option 1</li>
<!-- Disabled -->
<button aria-disabled="true">Submit</button>
<!-- Described by -->
<input aria-describedby="help-text">
<span id="help-text">Enter your full name</span>
<!-- Invalid -->
<input aria-invalid="true" aria-errormessage="error-msg">
<span id="error-msg">This field is required</span>Common ARIA Patterns
Modal Dialog
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Confirm Action</h2>
<!-- content -->
</div>Tabs
<div role="tablist" aria-label="Settings">
<button
role="tab"
aria-selected="true"
aria-controls="panel-1"
>General</button>
</div>
<div
role="tabpanel"
id="panel-1"
aria-labelledby="tab-1"
>...</div>Combobox/Autocomplete
<input
role="combobox"
aria-expanded="true"
aria-controls="listbox"
aria-activedescendant="option-2"
>
<ul role="listbox" id="listbox">
<li role="option" id="option-1">Option 1</li>
<li role="option" id="option-2">Option 2</li>
</ul>Testing Accessibility
Automated Testing
Use tools to catch common issues:
- axe DevTools - Browser extension for accessibility audits
- Lighthouse - Built into Chrome DevTools
- WAVE - Web accessibility evaluation tool
- eslint-plugin-jsx-a11y - Linting for React projects
Manual Testing
Automated tools catch only ~30% of issues. Always test manually:
- Keyboard-only navigation - Unplug your mouse and navigate
- Screen reader testing - Use VoiceOver (Mac), NVDA (Windows), or JAWS
- Zoom testing - Test at 200% and 400% zoom levels
- Color contrast - Check with contrast checker tools
- Reduced motion - Test with
prefers-reduced-motion
Screen Reader Testing Checklist
Motion and Animation
Respect user preferences for reduced motion:
/* Default animation */
.df-modal {
animation: fadeIn 0.3s ease;
}
/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
.df-modal {
animation: none;
}
}// Check preference in JavaScript
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (!prefersReducedMotion) {
// Run animation
}