| name | accessibility |
| description | Enforce WCAG 2.1 AA (keyboard, ARIA, focus, contrast, semantic HTML). Use for accessibility work, pre-ship UI checks, and as a light pass on every UI build or design review โ including "a11y", "keyboard", "screen reader", or shipping user-facing changes, without requiring a slash command. |
Accessibility
Overview
Every user-facing UI must meet WCAG 2.1 AA. Keyboard, labels, contrast, and semantics are required โ not optional polish.
When to Use
/design audits
- Light check during
/ui builds
- Dedicated accessibility fixes
Workflow
1. Keyboard
- All interactive elements focusable (native
<button>, <a>, or tabIndex={0} + key handler)
- Logical focus order; visible focus indicators (
focus-visible outline)
- Focus trapped in modals; restore to trigger on close
- Skip link to main content when long nav exists
Recipe โ focus trap (vanilla):
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
modal.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
first?.focus();
}
Recipe โ skip link:
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>
2. ARIA / labels
- Form inputs have visible labels (
<label for="id"> or wrapping <label>)
- Icon-only buttons have
aria-label
- Toggle states use
aria-pressed, aria-expanded, aria-selected
- Loading announced via
aria-busy="true" on the loading container
- Live regions (
aria-live="polite") for dynamic content updates (toasts, search results count)
Recipe โ live region for dynamic count:
<div aria-live="polite" aria-atomic="true" class="sr-only">
<span id="result-count">12 results found</span>
</div>
Recipe โ icon button with Reicon:
<button type="button" aria-label="Close dialog">
<re-icon icon="close-circle" size="20" aria-hidden="true"></re-icon>
</button>
3. Color & contrast
- Normal text โฅ 4.5:1 contrast ratio
- Large text (18px+ or 14px+ bold) โฅ 3:1
- Interactive focus indicators โฅ 3:1 against adjacent colors
- Do not rely on color alone for state โ add icon, text, or pattern
Decision tree โ contrast checking:
Is it text?
YES โ Normal text: 4.5:1 | Large text: 3:1
NO โ Is it a UI component or graphic?
YES โ 3:1 against adjacent color
NO โ Decorative โ no requirement
4. Semantics
- One
<h1> per page; sequential heading levels (h1 โ h2 โ h3, never skip)
- Landmarks:
<header>, <nav>, <main>, <footer>, <aside>
- Multiple
<nav> โ each needs aria-label (e.g. "Main navigation", "Footer links")
- Lists use
<ul>/<ol>/<dl> โ not styled divs
<dialog> for modals (native focus management + backdrop)
5. Dialog / Modal a11y (recipe)
<dialog id="my-dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm" autofocus>Confirm</button>
</form>
</dialog>
<script>
document.getElementById('my-dialog').showModal();
</script>
6. Color-blind safe patterns
| State | Don't (color only) | Do (color + indicator) |
|---|
| Error | Red border | Red border + error icon + text |
| Success | Green text | Green text + checkmark icon |
| Required | Red asterisk only | Asterisk + "(required)" text |
| Active tab | Blue color | Blue + underline/weight + aria-selected |
| Disabled | Gray color | Gray + disabled attr + reduced opacity |
7. Touch & tap targets
- Interactive elements โฅ 44ร44px on mobile (WCAG 2.5.8 target size)
- โฅ 40ร40px acceptable on dense desktop UIs
- No overlapping tap targets
- Adequate spacing between adjacent interactive elements (โฅ 8px gap)
Quick audit method
Run in this order (5-minute check):
- Tab through โ can you reach everything? Is focus visible?
- Screen reader โ does heading structure make sense? Are buttons/links announced correctly?
- Contrast check โ run browser DevTools contrast checker or axe
- Zoom to 200% โ does layout still work?
- Color only โ remove color perception (Chrome DevTools โ Rendering โ Emulate vision deficiency)
Checklist
Depth
Full checklist: references/accessibility-checklist.md.
Contrast & color-alone rules in context: references/ux-foundations.md.