| name | a11y |
| description | Accessibility guide (WCAG 2.1/2.2, Level AโAAA). Trigger: When building UI components, interactive elements, or auditing accessibility compliance. |
| license | Apache 2.0 |
| metadata | {"version":"1.2","type":"domain","allowed-tools":["file-reader"]} |
Accessibility (a11y)
Ensures WCAG 2.1/2.2 Level AA compliance: semantic structure, ARIA, contrast, keyboard nav.
When to Use
- Building UI components with interactive elements
- Implementing forms, modals, or custom widgets
- Adding dynamic content or live regions
- Ensuring keyboard navigation or reviewing accessibility compliance
- Auditing components or pages for WCAG 2.0/2.1/2.2 compliance
Don't use for:
- Tech-specific implementation (react, html skills)
- Backend logic (no UI)
Critical Patterns
โ
REQUIRED: Document Language โ SC 3.1.1 ยท Level A
<html lang="en">
<html lang="es-MX">
Rule: Always set lang on <html>. Missing lang causes screen readers to mispronounce all content.
โ
REQUIRED: Semantic HTML Elements โ SC 1.3.1 ยท Level A
<nav aria-label="Primary navigation">
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<article>Content</article>
</main>
<button onClick="{action}">Submit</button>
<div class="nav">
<div onClick="{navigate}">Home</div>
</div>
โ
REQUIRED: Keyboard Accessibility โ SC 2.1.1 ยท Level A
<button onClick={handleClick} onKeyDown={(e) => e.key === 'Enter' && handleClick()}>
<div onClick={handleClick}> // Not keyboard accessible
โ
REQUIRED: Form Labels โ SC 1.3.1, SC 3.3.2 ยท Level A
<label htmlFor="email">Email Address</label>
<input id="email" type="email" />
<div>Email Address</div>
<input type="email" />
โ
REQUIRED: Alt Text for Images โ SC 1.1.1 ยท Level A
<img src="chart.png" alt="Sales increased 25% in Q4" />
<img src="border.png" alt="" />
<img src="chart.png" />
โ
REQUIRED: SVG Accessibility โ SC 1.1.1 ยท Level A
SVG loaded as <img> respects alt. SVG inline or via SVGR (React) does not โ use role and aria-label directly.
<!-- Informative SVG -->
<svg role="img" aria-label="Company logo" focusable="false">
<title>Company logo</title>
</svg>
<!-- Decorative SVG -->
<svg aria-hidden="true" focusable="false">...</svg>
<Logo alt="Company logo" />
<Logo role="img" aria-label="Company logo" focusable="false" />
โ
REQUIRED: Disclosure Pattern (Accordion / Expandable) โ SC 4.1.2 ยท Level A
<button aria-expanded={isOpen} aria-controls="panel-id">
Details {}
</button>
<div id="panel-id" hidden={!isOpen}>Panel content</div>
Rules: aria-controls must match the panel id. Do not change the button's accessible name based on open/closed state.
โ
REQUIRED: Form Validation Errors โ SC 3.3.1 Level A ยท SC 3.3.3 Level AA
<label for="email">Email <span aria-hidden="true">*</span></label>
<input id="email" type="email" aria-required="true" aria-invalid="true"
aria-describedby="email-error" />
<span id="email-error" role="alert">
Enter a valid email address (e.g. user@example.com)
</span>
<div role="alert" tabindex="-1" id="error-summary">
<h2>3 errors prevented submission:</h2>
<ul><li><a href="#email">Email: Enter a valid address</a></li></ul>
Rules: aria-invalid="true" on the input (not the error span). On submit with errors, move focus to error summary (element.focus(), needs tabindex="-1").
โ
REQUIRED: Dynamic Page / SPA Navigation โ SC 2.4.2/2.4.3 Level A ยท SC 4.1.3 Level AA
document.title = `${pageTitle} | My App`;
announcer.textContent = '';
announcer.textContent = pageTitle;
document.querySelector('main')?.focus();
<div aria-live="polite" aria-atomic="true" class="sr-only" id="route-announcer"></div>
<main id="main-content" tabindex="-1">...</main>
Rules: <main> needs tabindex="-1" to be programmatically focusable. Do NOT move focus to <body>.
Conventions
Framework-native first: MUI, Radix UI, React Aria, Headless UI ship accessible primitives โ use them before implementing manually.
Semantic HTML: <nav>, <main>, <article>, <aside>, <footer> ยท heading hierarchy h1โh2โh3 (no skipping) ยท <button> for actions, <a> for navigation.
ARIA: Only when semantic HTML is insufficient. Common: aria-label, aria-labelledby, aria-describedby, aria-live, aria-current="page".
Keyboard: All interactive elements reachable ยท logical tab order ยท visible focus indicators ยท Escape closes modals/dropdowns.
Contrast (SC 1.4.3 / 1.4.11):
| Element | AA | AAA |
|---|
| Normal text | 4.5:1 | 7:1 |
| Large text (โฅ18pt / โฅ14pt bold) | 3:1 | 4.5:1 |
| UI components, focus indicators | 3:1 | โ |
| Disabled / decorative | none | โ |
Touch targets: 24ร24px min (WCAG 2.2), 44ร44px recommended.
Decision Tree
Does the element convey meaning visually (image, icon, SVG, badge, chart)?
โ Purely decorative (adds no information)?
โ img: alt="" | SVG inline: aria-hidden="true" focusable="false"
โ Informative?
โ img: write descriptive alt text (SC 1.1.1)
โ SVG inline: role="img" + aria-label + title element
โ Icon button: aria-label on button + aria-hidden on icon
Is color the only way information is communicated?
โ Error/success state conveyed only by color?
โ Add icon, text label, or pattern alongside color (SC 1.4.1)
โ Chart series distinguishable only by color?
โ Add patterns, direct labels, or textures
โ Link differs from surrounding text only by color?
โ Add underline or distinct non-color visual cue
Is contrast sufficient?
โ Element is disabled or purely decorative?
โ No contrast requirement
โ Text or text in image?
โ Large text (18pt+ or 14pt+ bold)?
โ Minimum 3:1 AA / 4.5:1 AAA (SC 1.4.3 / 1.4.6)
โ Normal text?
โ Minimum 4.5:1 AA / 7:1 AAA (SC 1.4.3 / 1.4.6)
โ UI component (input border, button outline, icon, chart graphic)?
โ Minimum 3:1 (SC 1.4.11)
โ Focus indicator?
โ Minimum 3:1 against adjacent colors (SC 1.4.11 / WCAG 2.2 SC 1.4.13)
Does the element have an accessible name?
โ Button or link with visible text?
โ Verify text is meaningful, not generic ("Click here", "Read more")
โ Button or link with icon only or no visible label?
โ Add aria-label or aria-labelledby (SC 4.1.2)
โ Form input?
โ Associate label via htmlFor/id or aria-labelledby (SC 1.3.1 / 3.3.2)
โ Custom widget (role="combobox", role="slider", etc.)?
โ Name via aria-label or aria-labelledby (SC 4.1.2)
Is the element keyboard operable?
โ Native element (button, a, input, select)?
โ Keyboard accessible by default โ verify logical tab order
โ Custom interactive element (div/span with onClick)?
โ Add role + tabindex="0" + keydown handler for Enter/Space (SC 2.1.1)
โ Functionality requires path-based gesture (drag, swipe)?
โ Provide single-pointer or keyboard alternative (SC 2.5.1)
Does the page have proper structure and landmarks?
โ html element missing lang attribute?
โ Set lang matching the page language (SC 3.1.1)
โ Heading levels skip or no h1 exists?
โ Fix hierarchy โ h1 once per page, then h2, h3 without gaps (SC 1.3.1)
โ No skip link as first focusable element?
โ Add skip link pointing to main content (SC 2.4.1)
โ Content not inside landmark regions?
โ Wrap in main, nav, header, footer, or aside (SC 1.3.6)
โ Page missing a descriptive, unique title?
โ Set document.title per page/view (SC 2.4.2)
Is focus visible and well-managed?
โ Focus indicator not visible or low contrast?
โ Ensure visible focus ring with 3:1 contrast (SC 2.4.7 / WCAG 2.2 SC 2.4.11)
โ Positive tabindex value (tabindex="1" or higher)?
โ Remove โ use DOM order to control tab sequence (SC 1.3.2)
โ Focusable element inside aria-hidden subtree?
โ Remove aria-hidden or make element non-focusable with inert or tabindex="-1"
โ Modal open โ is focus trapped and restored on close?
โ Trap focus in modal + restore to trigger on close (SC 2.1.2)
Is dynamic content properly announced?
โ Non-urgent update (search results, lazy-loaded content)?
โ aria-live="polite"
โ Critical alert or error message?
โ aria-live="assertive" or role="alert"
โ Entire region replaces its content at once?
โ aria-atomic="true"
โ Status message not in a dialog (toast, save confirmation)?
โ role="status" or role="alert" without moving focus (SC 4.1.3)
Is this a form with validation?
โ Required field?
โ aria-required="true" or native required attribute (SC 3.3.2)
โ Field has validation error?
โ aria-invalid="true" + aria-describedby pointing to error span + role="alert"
โ Multi-field form submitted with errors?
โ Move focus to error summary (tabindex="-1") + role="alert" (SC 3.3.1)
Is content adaptable and not reliant on sensory characteristics alone?
โ Instructions reference only shape, color, size, or position?
โ Add text alternative (SC 1.3.3)
โ Reading or operation order depends on visual layout?
โ Verify DOM order matches visual order (SC 1.3.2)
โ Content breaks or clips when zoomed to 400%?
โ Ensure reflow at 1280px/400% without horizontal scroll (SC 1.4.10)
โ Content clips when text spacing is increased?
โ No fixed containers that overflow on spacing change (SC 1.4.12)
Is there audio or video content?
โ Video with dialogue or meaningful audio?
โ Provide synchronized captions (SC 1.2.2 AA)
โ Audio-only content (podcast, recording)?
โ Provide text transcript (SC 1.2.1 A)
โ Video has important visual info not described in audio?
โ Provide audio description (SC 1.2.5 AA)
โ Media autoplays for more than 3 seconds?
โ Provide pause, stop, or mute mechanism (SC 1.4.2)
Does content flash or blink?
โ Flashes more than 3 times per second?
โ Remove or reduce below threshold (SC 2.3.1)
โ Blinks indefinitely?
โ Remove blink or provide mechanism to stop (SC 2.2.2)
Is the markup valid and role/state/property correct?
โ Duplicate id attributes in the DOM?
โ Remove duplicates โ id must be unique per page (SC 4.1.1)
โ ARIA role, state, or property has invalid value?
โ Fix to spec-valid value per WAI-ARIA (SC 4.1.2)
โ Required ARIA attributes missing for a role?
โ Add missing attributes per WAI-ARIA spec (SC 4.1.2)
SPA / route change?
โ Update document.title to reflect the new page/view
โ Announce via persistent aria-live="polite" region
โ Move focus to main or h1 with tabindex="-1"
Custom widget (tabs, combobox, slider, tree, datepicker)?
โ Follow WAI-ARIA Authoring Practices for that pattern
โ Arrow key navigation, Escape, Enter/Space
โ See references/wai-aria-patterns.md
Touch / pointer interaction?
โ Target size below 24x24px?
โ Increase to minimum 24x24px, target 44x44px (WCAG 2.2 SC 2.5.8)
โ Gesture requires specific path (drag, pinch, swipe)?
โ Provide single-pointer or keyboard alternative (SC 2.5.1)
Example
Accessible modal dialog: focus trap, ARIA labels, and keyboard navigation applied together.
function ConfirmDeleteModal({ isOpen, onClose, onConfirm }: ModalProps) {
const firstFocusRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) firstFocusRef.current?.focus();
}, [isOpen]);
if (!isOpen) return null;
return (
<div role="dialog" aria-modal="true" aria-labelledby="modal-title"
onKeyDown={(e) => e.key === 'Escape' && onClose()}>
<h2 id="modal-title">Delete this item?</h2>
<p id="modal-desc">This action cannot be undone.</p>
<button ref={firstFocusRef} aria-describedby="modal-desc"
onClick={onConfirm}>Confirm Delete</button>
<button onClick={onClose}>Cancel</button>
);
}
Patterns applied: role="dialog", aria-modal, aria-labelledby, aria-describedby, focus on open, Escape to dismiss.
Edge Cases
WCAG 2.2 updates: 24ร24px min target size; focus indicators 3:1 contrast; provide pointer alternatives for drag; CAPTCHAs need alternatives (no cognitive function tests).
Skip links: First focusable element, visually hidden, revealed on focus.
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>
Apply .skip-link:focus { position: fixed; top: 0; clip: auto; padding: 0.5rem 1rem; } to reveal visually.
sr-only pattern: Visually hidden but screen-reader accessible. Use for icon-button labels and status announcements. Standard CSS: position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
ARIA live regions throttling: Rapid updates may be throttled. Debounce or use aria-atomic="true".
Focus trap issues: Libraries like React may interfere with focus management. Test focus trap explicitly in modals.
Custom controls: For complex widgets (datepickers, sliders, menus, tabs), follow WAI-ARIA Authoring Practices. See references/wai-aria-patterns.md.
Resources