Files
gh-christianlouis-docuelevate/docs/AccessibilityGuide.md
T
copilot-swe-agent[bot] 1f48503217 docs(a11y): add comprehensive AccessibilityGuide.md
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>
2026-02-28 16:18:35 +00:00

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

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", and role="menuitem"
  • The mobile menu toggle has aria-label="Toggle navigation menu" and aria-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

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

  1. Verify light mode contrast at WebAIM Contrast Checker
  2. Verify dark mode contrast for the corresponding dark override
  3. 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:

  1. Move focus into the dialog (first focusable element or the dialog itself)
  2. Trap focus within the dialog while open
  3. 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:

  1. Keyboard navigation: Tab through all interactive elements; confirm logical focus order
  2. Screen reader: Test with a screen reader (VoiceOver on macOS, NVDA on Windows, Orca on Linux)
  3. Color contrast: Check new color combinations with WebAIM Contrast Checker
  4. Zoom: Test at 200% zoom; ensure content remains usable
  5. 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 alt attributes
  • All decorative icons have aria-hidden="true"
  • All icon-only buttons have aria-label
  • All form inputs have associated labels (via for/id or aria-label)
  • All tables have aria-label or <caption> and scope on headers
  • All modals have role="dialog", aria-modal="true", and aria-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/ --lint passes with zero errors
  • Page heading hierarchy is correct (h1 → h2 → h3, no skipped levels)