WCAG 2.1 AA compliance patterns, screen reader compatibility, keyboard navigation, and ARIA best practices. Use when implementing accessible interfaces, reviewing UI components, or auditing accessibility compliance. Covers semantic HTML, focus management, color contrast, and assistive technology testing.
/plugin marketplace add rsmdt/the-startup/plugin install team@the-startupThis skill inherits all available tools. When active, it can use any tool Claude has access to.
checklists/wcag-checklist.mdThis skill provides comprehensive guidance for implementing WCAG 2.1 AA compliance across digital products. It establishes patterns for semantic markup, assistive technology compatibility, and inclusive interaction design.
All accessibility work follows the four POUR principles:
Perceivable - Information must be presentable in ways users can perceive
Operable - Interface components must be operable by all users
Understandable - Information and operation must be understandable
Robust - Content must be robust enough for diverse user agents
Semantic HTML is the foundation of accessibility. ARIA should enhance, never replace, proper markup.
Correct approach:
<button type="submit">Submit Form</button>
Incorrect approach:
<div role="button" tabindex="0" onclick="submit()">Submit Form</div>
Use native HTML elements whenever possible:
<button> for actions<a href> for navigation<input>, <select>, <textarea> for form controls<nav>, <main>, <aside>, <header>, <footer> for landmarks<h1> through <h6> for heading hierarchyBuild accessibility in layers:
Every page requires proper document structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Descriptive Page Title - Site Name</title>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header role="banner">
<nav aria-label="Main navigation">
<!-- Navigation content -->
</nav>
</header>
<main id="main-content" role="main">
<h1>Page Heading</h1>
<!-- Page content -->
</main>
<footer role="contentinfo">
<!-- Footer content -->
</footer>
</body>
</html>
Maintain logical heading order without skipping levels:
<h1>Main Page Title</h1>
<h2>Section Title</h2>
<h3>Subsection Title</h3>
<h3>Another Subsection</h3>
<h2>Another Section</h2>
<h3>Subsection</h3>
<h4>Sub-subsection</h4>
Provide skip links for keyboard users to bypass repetitive content:
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px 16px;
background: #000;
color: #fff;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Focus indicators must be visible and meet contrast requirements:
:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* Enhanced focus for better visibility */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 3px;
box-shadow: 0 0 0 6px rgba(0, 95, 204, 0.25);
}
/* Never remove focus indicators entirely */
:focus:not(:focus-visible) {
outline: 2px solid transparent;
box-shadow: 0 0 0 2px #005fcc;
}
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === firstFocusable) {
lastFocusable.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastFocusable) {
firstFocusable.focus();
e.preventDefault();
}
}
});
}
| Key | Action |
|---|---|
| Tab | Move focus to next focusable element |
| Shift+Tab | Move focus to previous focusable element |
| Enter/Space | Activate buttons and links |
| Arrow keys | Navigate within components (menus, tabs, radio groups) |
| Escape | Close modals, menus, dropdowns |
| Home/End | Move to first/last item in lists |
Ensure logical tab order follows visual order:
<!-- Correct: Tab order matches visual order -->
<form>
<label for="name">Name</label>
<input id="name" type="text">
<label for="email">Email</label>
<input id="email" type="email">
<button type="submit">Submit</button>
</form>
Never use positive tabindex values. Only use:
tabindex="0" to make non-interactive elements focusabletabindex="-1" to make elements programmatically focusable but not in tab orderUse ARIA only when native HTML cannot achieve the required accessibility:
Common roles for custom widgets:
<!-- Tab pattern -->
<div role="tablist" aria-label="Settings tabs">
<button role="tab" aria-selected="true" aria-controls="panel1" id="tab1">
General
</button>
<button role="tab" aria-selected="false" aria-controls="panel2" id="tab2">
Security
</button>
</div>
<div role="tabpanel" id="panel1" aria-labelledby="tab1">
<!-- Panel content -->
</div>
<div role="tabpanel" id="panel2" aria-labelledby="tab2" hidden>
<!-- Panel content -->
</div>
| Attribute | Purpose | Example |
|---|---|---|
aria-expanded | Indicates expandable element state | aria-expanded="false" |
aria-selected | Indicates selection state | aria-selected="true" |
aria-pressed | Indicates toggle button state | aria-pressed="mixed" |
aria-hidden | Hides content from assistive tech | aria-hidden="true" |
aria-live | Announces dynamic content | aria-live="polite" |
aria-describedby | References descriptive content | aria-describedby="hint" |
aria-labelledby | References labeling content | aria-labelledby="heading" |
Announce dynamic content changes:
<!-- Polite: Announces when user is idle -->
<div aria-live="polite" aria-atomic="true">
Form saved successfully
</div>
<!-- Assertive: Interrupts immediately (use sparingly) -->
<div aria-live="assertive" role="alert">
Error: Please correct the highlighted fields
</div>
<!-- Status messages -->
<div role="status">
Loading... 50% complete
</div>
Every form control requires a label:
<!-- Explicit label association -->
<label for="username">Username</label>
<input type="text" id="username" name="username"
aria-describedby="username-hint">
<span id="username-hint">Must be 3-20 characters</span>
<!-- Grouped controls with fieldset -->
<fieldset>
<legend>Shipping Address</legend>
<label for="street">Street</label>
<input type="text" id="street">
<!-- More fields -->
</fieldset>
Provide clear, actionable error messages:
<label for="email">Email</label>
<input type="email" id="email"
aria-invalid="true"
aria-describedby="email-error">
<span id="email-error" role="alert">
Please enter a valid email address (example: user@domain.com)
</span>
Error message requirements:
aria-invalid on the fieldrole="alert"<label for="name">
Name <span aria-hidden="true">*</span>
<span class="sr-only">(required)</span>
</label>
<input type="text" id="name" required aria-required="true">
Provide meaningful alt text for informative images:
<!-- Informative image -->
<img src="chart.png" alt="Q3 revenue increased 15% compared to Q2">
<!-- Decorative image -->
<img src="divider.png" alt="" role="presentation">
<!-- Complex image with extended description -->
<figure>
<img src="flowchart.png" alt="User registration process"
aria-describedby="flowchart-desc">
<figcaption id="flowchart-desc">
The registration process begins with email verification,
followed by profile creation, and ends with account activation.
</figcaption>
</figure>
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
<track kind="descriptions" src="descriptions.vtt" srclang="en"
label="Audio descriptions">
</video>
| Content Type | Minimum Ratio (AA) | Enhanced Ratio (AAA) |
|---|---|---|
| Normal text (< 18pt) | 4.5:1 | 7:1 |
| Large text (>= 18pt or 14pt bold) | 3:1 | 4.5:1 |
| UI components and graphics | 3:1 | N/A |
<!-- Bad: Color only indicates error -->
<input style="border-color: red">
<!-- Good: Color plus icon and text -->
<input aria-invalid="true" aria-describedby="error">
<span id="error">
<svg aria-hidden="true"><!-- Error icon --></svg>
Invalid email format
</span>
/* Reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Run automated tools to catch common issues:
Automated testing catches approximately 30-40% of issues.
Test with multiple screen readers:
| Platform | Screen Reader | Browser |
|---|---|---|
| Windows | NVDA (free) | Firefox, Chrome |
| Windows | JAWS | Chrome, Edge |
| macOS | VoiceOver | Safari |
| iOS | VoiceOver | Safari |
| Android | TalkBack | Chrome |
Screen reader testing checklist:
Perform manual audit against WCAG 2.1 AA success criteria. See checklists/wcag-checklist.md for the complete checklist.
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Action</h2>
<p>Are you sure you want to proceed?</p>
<button type="button">Cancel</button>
<button type="button">Confirm</button>
</div>
Requirements:
<div class="accordion">
<h3>
<button aria-expanded="true" aria-controls="section1">
Section 1
</button>
</h3>
<div id="section1" role="region" aria-labelledby="section1-heading">
<!-- Content -->
</div>
</div>
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li>
<button aria-expanded="false" aria-haspopup="true">
Products
</button>
<ul>
<li><a href="/products/a">Product A</a></li>
<li><a href="/products/b">Product B</a></li>
</ul>
</li>
</ul>
</nav>
<table>
<caption>Q3 Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Units Sold</th>
<th scope="col">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North</th>
<td>1,234</td>
<td>$45,678</td>
</tr>
</tbody>
</table>
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.