Add docs/AccessibilityGuide.md with WCAG 2.1 Level AA standards, ARIA patterns, keyboard navigation requirements, color contrast guidelines, form/table/modal accessibility patterns, testing instructions, and a developer checklist for UI changes. Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
9.7 KiB
Accessibility Guide
DocuElevate targets WCAG 2.1 Level AA compliance across all user-facing pages. This guide documents the accessibility standards, patterns, and practices used in the project.
Table of Contents
- Standards Overview
- ARIA Patterns Used
- Keyboard Navigation
- Color and Contrast
- Forms and Inputs
- Tables
- Modals and Dialogs
- Dynamic Content
- Images and Icons
- Testing and Validation
- Developer Checklist
Standards Overview
DocuElevate follows WCAG 2.1 Level AA guidelines. The key principles are:
| Principle | Description |
|---|---|
| Perceivable | Information and UI components must be presentable in ways all users can perceive |
| Operable | UI components and navigation must be operable via keyboard and assistive technologies |
| Understandable | Information and UI operation must be understandable |
| Robust | Content must be robust enough for a wide variety of user agents and assistive technologies |
ARIA Patterns Used
Landmarks
base.html provides the following landmark structure on every page:
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav aria-label="Main navigation">...</nav>
<main id="main-content">{% block content %}{% endblock %}</main>
<footer role="contentinfo">
<nav aria-label="Footer navigation">...</nav>
</footer>
Navigation
- Active page links use
aria-current="page" - The admin dropdown uses
aria-haspopup="true",aria-expanded,role="menu", androle="menuitem" - The mobile menu toggle has
aria-label="Toggle navigation menu"andaria-expanded
Decorative Icons
Font Awesome icons that appear next to descriptive text must have aria-hidden="true":
<!-- Correct: icon is decorative, text provides meaning -->
<a href="/settings">
<i class="fas fa-cog" aria-hidden="true"></i> Settings
</a>
<!-- Correct: icon-only button needs aria-label -->
<button aria-label="Delete file">
<i class="fas fa-trash" aria-hidden="true"></i>
</button>
Keyboard Navigation
Skip Link
Every page inherits a skip-to-content link from base.html that becomes visible on keyboard focus:
.skip-link:focus {
position: fixed;
top: 0;
left: 0;
/* ... visible styles ... */
}
Focus Indicators
Global focus-visible styles are defined in frontend/static/styles.css:
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
Warning: Never remove or suppress focus indicators with
outline: noneunless you provide an equally visible alternative.
Interactive Custom Elements
When using <div> or <span> as interactive elements (avoid when possible), ensure:
<div
role="button"
tabindex="0"
onclick="handleClick()"
onkeydown="if(event.key==='Enter'||event.key===' '){handleClick();}"
aria-label="Descriptive action name"
>
Color and Contrast
Minimum Ratios (WCAG 1.4.3)
| Text Type | Minimum Contrast Ratio |
|---|---|
| Normal text (< 18pt) | 4.5:1 |
| Large text (≥ 18pt or ≥ 14pt bold) | 3:1 |
| UI components and graphical objects | 3:1 |
Dark Mode
Dark mode overrides in styles.css are verified for WCAG AA contrast ratios. When adding new color values:
- Verify light mode contrast at WebAIM Contrast Checker
- Verify dark mode contrast for the corresponding dark override
- Never rely on color alone — always pair with text, icons, or patterns
Status Badges
Status indicators use both color and text:
<span class="status-badge status-completed">Completed</span>
<span class="status-badge status-failed">Failed</span>
Forms and Inputs
Labels (WCAG 1.3.1, 3.3.2)
Every form input must have an associated label:
<!-- Preferred: explicit label with for/id -->
<label for="username" class="block text-sm font-medium">Username</label>
<input type="text" id="username" name="username" required />
<!-- Hidden inputs: use aria-label -->
<input type="file" class="hidden" aria-label="Select files to upload" />
Error Messages (WCAG 3.3.1)
Error messages must be announced to screen readers:
<div role="alert" class="bg-red-100 text-red-700">
<p>{{ error_message }}</p>
</div>
Search Forms
Use role="search" and proper labelling:
<div class="search-box" role="search">
<label for="search-input" class="sr-only">Search documents</label>
<input type="search" id="search-input" placeholder="Search..." />
<button type="button" aria-label="Search documents">
<i class="fas fa-search" aria-hidden="true"></i> Search
</button>
</div>
Tables
Required Structure (WCAG 1.3.1)
<table aria-label="File records">
<thead>
<tr>
<th scope="col">Filename</th>
<th scope="col">Size</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>document.pdf</td>
<td>1.2 MB</td>
<td>Completed</td>
</tr>
</tbody>
</table>
Sortable Columns
Add aria-sort to indicate the current sort state:
<th scope="col" class="sortable" aria-sort="ascending">
Filename <span class="sort-indicator active">▲</span>
</th>
Modals and Dialogs
Required Attributes (WCAG 4.1.2)
<div
id="deleteModal"
class="modal"
role="dialog"
aria-modal="true"
aria-labelledby="deleteModalTitle"
>
<div class="modal-content">
<h2 id="deleteModalTitle">Confirm Deletion</h2>
<p>Are you sure you want to delete this file?</p>
<button>Cancel</button>
<button>Delete</button>
</div>
</div>
Focus Management
When opening a modal:
- Move focus into the dialog (first focusable element or the dialog itself)
- Trap focus within the dialog while open
- Return focus to the trigger element when closed
Dynamic Content
Live Regions (WCAG 4.1.3)
Content that updates dynamically must be announced to screen readers:
<!-- Upload progress -->
<div id="uploadProgress" role="status" aria-live="polite">
<!-- Dynamic progress updates appear here -->
</div>
<!-- Status messages -->
<div id="statusMessage" aria-live="polite">
<!-- Success/error messages appear here -->
</div>
<!-- Search results -->
<div id="search-results" aria-live="polite" aria-relevant="additions removals">
<!-- Results injected by JavaScript -->
</div>
| Attribute | Use When |
|---|---|
aria-live="polite" |
Updates that don't require immediate attention (search results, progress) |
aria-live="assertive" |
Critical updates that require immediate attention (errors) |
role="status" |
Status information (equivalent to aria-live="polite") |
role="alert" |
Important messages that require immediate attention |
Images and Icons
Content Images
<img src="/static/images/logo.svg" alt="DocuElevate Logo" class="h-16" />
Decorative Images
<img src="/static/decoration.jpg" alt="" />
Icon-Only Buttons
<button aria-label="View file details" title="View details">
<i class="fas fa-info-circle" aria-hidden="true"></i>
</button>
Icons with Adjacent Text
<a href="/upload">
<i class="fas fa-upload" aria-hidden="true"></i> Upload
</a>
Testing and Validation
Automated Checks (CI Pipeline)
The CI pipeline runs djlint on every pull request:
# Run locally before committing
djlint frontend/templates/ --lint
Configuration is in pyproject.toml under [tool.djlint]. The linter enforces:
| Rule | Description |
|---|---|
| H005 | <html> tag must have lang attribute |
| H013 | <img> tag must have alt attribute |
| H016 | Document must have <title> tag |
| H025 | Tag should not be an orphan |
| H026 | Empty id and class attributes should be removed |
Manual Testing
For each new feature or UI change, verify:
- Keyboard navigation: Tab through all interactive elements; confirm logical focus order
- Screen reader: Test with a screen reader (VoiceOver on macOS, NVDA on Windows, Orca on Linux)
- Color contrast: Check new color combinations with WebAIM Contrast Checker
- Zoom: Test at 200% zoom; ensure content remains usable
- Responsive: Test at mobile viewport widths (320px, 375px)
Browser Testing Tools
- Chrome DevTools Accessibility panel — audit ARIA attributes and contrast
- axe DevTools browser extension — automated WCAG scanning
- Lighthouse — includes accessibility audit in Performance tab
Developer Checklist
Use this checklist when creating or modifying UI templates:
- All images have appropriate
altattributes - All decorative icons have
aria-hidden="true" - All icon-only buttons have
aria-label - All form inputs have associated labels (via
for/idoraria-label) - All tables have
aria-labelor<caption>andscopeon headers - All modals have
role="dialog",aria-modal="true", andaria-labelledby - Dynamic content areas have
aria-live="polite"or appropriate role - Color contrast meets 4.5:1 for normal text, 3:1 for large text
- All interactive elements are keyboard-accessible
- Focus indicators are visible on all interactive elements
djlint frontend/templates/ --lintpasses with zero errors- Page heading hierarchy is correct (h1 → h2 → h3, no skipped levels)