|
| 1 | +# WCAG 2.2 Level AA -- Static Code Review Checklist |
| 2 | + |
| 3 | +This checklist covers WCAG 2.2 Level AA success criteria that can be detected through code review. Criteria that require runtime testing (e.g., timing, audio descriptions) are excluded. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Principle 1: Perceivable |
| 8 | + |
| 9 | +### 1.1.1 Non-text Content (Level A) |
| 10 | +**Urgency: urgent** |
| 11 | +- `<img>` elements must have an `alt` attribute. Decorative images should use `alt=""` or `role="presentation"`. |
| 12 | +- Icon-only buttons/links must have an accessible name (`aria-label`, `aria-labelledby`, or visually hidden text). |
| 13 | +- `<svg>` elements used as content must have `role="img"` and an accessible name (`aria-label` or `<title>`). |
| 14 | +- Font icon elements (e.g., `<i className="fa fa-..."`) used as interactive controls must have an accessible name. |
| 15 | +- **Exception:** Purely decorative elements that don't convey information. |
| 16 | + |
| 17 | +### 1.3.1 Info and Relationships (Level A) |
| 18 | +**Urgency: urgent** |
| 19 | +- Use semantic HTML elements (`<nav>`, `<main>`, `<header>`, `<footer>`, `<section>`, `<article>`) instead of generic `<div>` for landmarks. |
| 20 | +- Headings (`<h1>`-`<h6>`) must follow a logical hierarchy -- no skipped levels. |
| 21 | +- Form inputs must have associated `<label>` elements (via `htmlFor`/`id`) or `aria-label`/`aria-labelledby`. |
| 22 | +- Data tables must use `<th>` with `scope` attributes. Do not use tables for layout. |
| 23 | +- Related form controls should be grouped with `<fieldset>` and `<legend>`. |
| 24 | +- Lists of items should use `<ul>`, `<ol>`, or `<dl>` -- not styled `<div>` sequences. |
| 25 | + |
| 26 | +### 1.3.2 Meaningful Sequence (Level A) |
| 27 | +**Urgency: suggestion** |
| 28 | +- DOM order should match the visual reading order. Avoid CSS that reorders content (`order`, `flex-direction: row-reverse`) in ways that break reading sequence. |
| 29 | + |
| 30 | +### 1.3.4 Orientation (Level AA) |
| 31 | +**Urgency: suggestion** |
| 32 | +- Do not restrict display to a single orientation via CSS or JS unless essential. |
| 33 | + |
| 34 | +### 1.3.5 Identify Input Purpose (Level AA) |
| 35 | +**Urgency: suggestion** |
| 36 | +- Form fields for personal data (name, email, phone, address, etc.) should have appropriate `autoComplete` attributes. |
| 37 | + |
| 38 | +### 1.4.1 Use of Color (Level A) |
| 39 | +**Urgency: urgent** |
| 40 | +- Color must not be the only visual means of conveying information (e.g., error states, required fields, status). Provide text, icons, or patterns as well. |
| 41 | + |
| 42 | +### 1.4.3 Contrast (Minimum) (Level AA) |
| 43 | +**Urgency: urgent** |
| 44 | +- Text color against its background must meet 4.5:1 ratio for normal text, 3:1 for large text (18pt+ or 14pt+ bold). |
| 45 | +- Check hardcoded color values in CSS/SCSS and inline styles. Flag combinations that are visually likely to fail (e.g., light gray on white). |
| 46 | +- **Note:** Exact contrast can only be verified with a tool; flag suspicious values for manual check. |
| 47 | + |
| 48 | +### 1.4.4 Resize Text (Level AA) |
| 49 | +**Urgency: suggestion** |
| 50 | +- Text sizes should use relative units (`rem`, `em`, `%`) rather than fixed `px` for body text. |
| 51 | +- Layouts should not break at 200% zoom. Avoid fixed-height containers with `overflow: hidden` on text content. |
| 52 | + |
| 53 | +### 1.4.11 Non-text Contrast (Level AA) |
| 54 | +**Urgency: suggestion** |
| 55 | +- UI components (form controls, buttons) and meaningful graphics must have at least 3:1 contrast against adjacent colors. |
| 56 | + |
| 57 | +### 1.4.13 Content on Hover or Focus (Level AA) |
| 58 | +**Urgency: suggestion** |
| 59 | +- Tooltips/popovers shown on hover must also be dismissible (Escape key), hoverable (user can move pointer to the tooltip), and persistent (don't disappear while the user is interacting). |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +## Principle 2: Operable |
| 64 | + |
| 65 | +### 2.1.1 Keyboard (Level A) |
| 66 | +**Urgency: urgent** |
| 67 | +- All interactive elements must be keyboard accessible. `onClick` on non-interactive elements (`<div>`, `<span>`) requires `onKeyDown`/`onKeyUp`, `tabIndex="0"`, and `role`. |
| 68 | +- Prefer semantic elements (`<button>`, `<a>`, `<input>`) over ARIA-enhanced `<div>`s. |
| 69 | +- Drag-and-drop must have a keyboard alternative. |
| 70 | +- Custom widgets must handle expected key interactions (e.g., arrow keys for menus/tabs, Space/Enter for buttons). |
| 71 | + |
| 72 | +### 2.1.2 No Keyboard Trap (Level A) |
| 73 | +**Urgency: urgent** |
| 74 | +- Modal dialogs must trap focus *within* the modal but allow dismissal via Escape. |
| 75 | +- Focus must not get stuck in any component. Verify that custom focus management doesn't prevent tabbing away. |
| 76 | + |
| 77 | +### 2.4.1 Bypass Blocks (Level A) |
| 78 | +**Urgency: suggestion** |
| 79 | +- Pages with repeated navigation should provide a "skip to main content" link or use landmark regions. |
| 80 | + |
| 81 | +### 2.4.2 Page Titled (Level A) |
| 82 | +**Urgency: suggestion** |
| 83 | +- Each page/view should set a descriptive `<title>` or use `document.title`. |
| 84 | + |
| 85 | +### 2.4.3 Focus Order (Level A) |
| 86 | +**Urgency: urgent** |
| 87 | +- `tabIndex` values greater than 0 disrupt natural focus order -- flag any `tabIndex` > 0. |
| 88 | +- Tab order must follow a logical sequence through the page. |
| 89 | + |
| 90 | +### 2.4.4 Link Purpose (Level A) |
| 91 | +**Urgency: suggestion** |
| 92 | +- Link text must describe the destination. Flag "click here", "here", "read more" without context. |
| 93 | +- If link text is generic, `aria-label` or `aria-describedby` should provide context. |
| 94 | + |
| 95 | +### 2.4.6 Headings and Labels (Level AA) |
| 96 | +**Urgency: suggestion** |
| 97 | +- Headings and labels must be descriptive. Flag empty headings or labels. |
| 98 | + |
| 99 | +### 2.4.7 Focus Visible (Level AA) |
| 100 | +**Urgency: urgent** |
| 101 | +- Do not remove focus indicators. Flag `outline: none`, `outline: 0`, or `:focus { outline: none }` without a replacement focus style. |
| 102 | +- Custom focus styles must be clearly visible. |
| 103 | + |
| 104 | +### 2.4.11 Focus Not Obscured (Minimum) (Level AA) -- NEW in 2.2 |
| 105 | +**Urgency: urgent** |
| 106 | +- When a component receives focus, it must not be entirely hidden by other content (e.g., sticky headers, footers, overlays). |
| 107 | +- Check for `position: fixed`/`sticky` elements that could cover focused items. Ensure `scroll-padding` or `scroll-margin` accounts for sticky elements. |
| 108 | + |
| 109 | +### 2.4.13 Focus Appearance (Level AAA, but recommended) |
| 110 | +**Urgency: suggestion** |
| 111 | +- Custom focus indicators should have at least a 2px solid outline with 3:1 contrast against adjacent colors. |
| 112 | + |
| 113 | +### 2.5.7 Dragging Movements (Level AA) -- NEW in 2.2 |
| 114 | +**Urgency: urgent** |
| 115 | +- Any functionality that uses dragging must provide a single-pointer alternative (e.g., up/down buttons to reorder, click-to-select then click-to-place). |
| 116 | +- **Exception:** Dragging is essential to the function (rare). |
| 117 | + |
| 118 | +### 2.5.8 Target Size (Minimum) (Level AA) -- NEW in 2.2 |
| 119 | +**Urgency: suggestion** |
| 120 | +- Interactive targets should be at least 24x24 CSS pixels, or have sufficient spacing so the target + spacing meets 24px. |
| 121 | +- **Exceptions:** Inline text links, targets where the size is determined by the user agent (e.g., default checkboxes), essential presentation. |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## Principle 3: Understandable |
| 126 | + |
| 127 | +### 3.1.1 Language of Page (Level A) |
| 128 | +**Urgency: suggestion** |
| 129 | +- HTML element should have a `lang` attribute (`<html lang="en">`). |
| 130 | + |
| 131 | +### 3.1.2 Language of Parts (Level AA) |
| 132 | +**Urgency: suggestion** |
| 133 | +- Content in a different language than the page should use `lang` attribute on its container. |
| 134 | + |
| 135 | +### 3.2.1 On Focus (Level A) |
| 136 | +**Urgency: urgent** |
| 137 | +- Focus must not trigger a change of context (e.g., page navigation, form submission, opening a new window). |
| 138 | + |
| 139 | +### 3.2.2 On Input (Level A) |
| 140 | +**Urgency: urgent** |
| 141 | +- Changing a form control value must not automatically trigger a change of context unless the user is informed beforehand. |
| 142 | +- Flag `onChange` handlers that submit forms or navigate without user confirmation. |
| 143 | + |
| 144 | +### 3.3.1 Error Identification (Level A) |
| 145 | +**Urgency: urgent** |
| 146 | +- Form errors must be described in text, not just color. Error messages must identify which field has the error. |
| 147 | +- Error messages must be programmatically associated with their inputs (`aria-describedby`, `aria-errormessage`, or `aria-invalid`). |
| 148 | + |
| 149 | +### 3.3.2 Labels or Instructions (Level A) |
| 150 | +**Urgency: urgent** |
| 151 | +- Form inputs must have visible labels. Placeholder text alone is not sufficient as a label. |
| 152 | +- Required fields must be indicated in a way that doesn't rely solely on color. |
| 153 | + |
| 154 | +### 3.3.3 Error Suggestion (Level AA) |
| 155 | +**Urgency: suggestion** |
| 156 | +- When an input error is detected, provide a suggestion for correction if possible. |
| 157 | + |
| 158 | +### 3.3.7 Redundant Entry (Level A) -- NEW in 2.2 |
| 159 | +**Urgency: suggestion** |
| 160 | +- Do not require users to re-enter information they have already provided in the same process/session. |
| 161 | +- **Exceptions:** Re-entering for security purposes, or when previously entered info is no longer valid. |
| 162 | + |
| 163 | +### 3.3.8 Accessible Authentication (Minimum) (Level AA) -- NEW in 2.2 |
| 164 | +**Urgency: urgent** |
| 165 | +- Authentication must not require cognitive function tests (e.g., CAPTCHA, puzzle) unless an alternative is provided (e.g., object recognition, personal content). |
| 166 | +- Allow pasting into password fields. Do not block password managers. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## Principle 4: Robust |
| 171 | + |
| 172 | +### 4.1.2 Name, Role, Value (Level A) |
| 173 | +**Urgency: urgent** |
| 174 | +- Custom components must expose correct ARIA roles, states, and properties. |
| 175 | +- `aria-expanded`, `aria-selected`, `aria-checked`, `aria-pressed` must accurately reflect component state. |
| 176 | +- `aria-hidden="true"` must not be set on focusable or interactive elements. |
| 177 | +- `role` values must be valid WAI-ARIA roles. |
| 178 | +- IDs referenced by `aria-labelledby`, `aria-describedby`, `aria-controls`, etc. must exist in the DOM. |
| 179 | + |
| 180 | +### 4.1.3 Status Messages (Level AA) |
| 181 | +**Urgency: suggestion** |
| 182 | +- Status messages (success, error, loading, progress) that don't receive focus must use `role="status"`, `role="alert"`, or `aria-live` regions so screen readers announce them. |
| 183 | +- Use `aria-live="polite"` for non-urgent updates, `aria-live="assertive"` for critical alerts. |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +## LabKey-Specific Patterns |
| 188 | + |
| 189 | +These patterns are common in the LabKey codebase and deserve extra attention: |
| 190 | + |
| 191 | +### React Components (`@labkey/components`, `@labkey/premium`) |
| 192 | +- Verify `Alert` components use appropriate ARIA roles. |
| 193 | +- Check `Modal`/`ModalDialog` components trap focus and are dismissible via Escape. |
| 194 | +- Ensure `Grid`/`QueryGrid` table components use proper `<table>` semantics with headers. |
| 195 | +- Check custom dropdown/select components for keyboard navigation (arrow keys, Escape, Enter). |
| 196 | + |
| 197 | +### JSP Pages |
| 198 | +- Verify `<labkey:form>` and `<labkey:input>` render with proper label associations. |
| 199 | +- Check `<labkey:link>` and `<labkey:button>` produce accessible HTML. |
| 200 | + |
| 201 | +### ExtJS Components |
| 202 | +- ExtJS components often lack accessibility. Flag custom ExtJS widgets that have no ARIA markup. |
| 203 | +- Verify ExtJS modals/windows are keyboard dismissible. |
0 commit comments