Help us improve
Share bugs, ideas, or general feedback.
From claude-dev-ecosystem
Provides WCAG 2.2 patterns for semantic HTML, ARIA roles, keyboard navigation, and screen reader support. Useful when auditing or building accessible web interfaces.
npx claudepluginhub oiranca/claude-dev-ecosystemHow this skill is triggered — by the user, by Claude, or both
Slash command
/claude-dev-ecosystem:accessibility-wcagThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```html
Provides WCAG 2.2 patterns for semantic HTML, ARIA roles, keyboard navigation, and screen reader support. Useful when auditing or building accessible web interfaces.
Provides WCAG 2.2 AA accessibility patterns for keyboard focus management, React Aria components, screen reader support, ARIA usage, focus traps, reduced motion, and cognitive inclusion. Use for compliant web apps.
Implements WCAG 2.1/2.2 compliance, ARIA patterns, keyboard navigation, focus management, and accessibility testing for web components.
Share bugs, ideas, or general feedback.
<!-- Use semantic elements instead of generic divs -->
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Product Details</h1>
<section aria-labelledby="specs-heading">
<h2 id="specs-heading">Specifications</h2>
<dl>
<dt>Weight</dt>
<dd>1.2 kg</dd>
<dt>Dimensions</dt>
<dd>30 x 20 x 10 cm</dd>
</dl>
</section>
</article>
</main>
<footer>
<p>© 2024 Company Name</p>
</footer>
Use <nav>, <main>, <article>, <section>, <aside> instead of <div> for landmarks. Screen readers use these to navigate the page.
function Modal({ isOpen, onClose, title, children }) {
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">{title}</h2>
<div>{children}</div>
<button onClick={onClose} aria-label="Close dialog">
<XIcon aria-hidden="true" />
</button>
</div>
);
}
function Tabs({ tabs, activeIndex, onChange }) {
return (
<div>
<div role="tablist" aria-label="Settings sections">
{tabs.map((tab, i) => (
<button
key={tab.id}
role="tab"
id={`tab-${tab.id}`}
aria-selected={i === activeIndex}
aria-controls={`panel-${tab.id}`}
tabIndex={i === activeIndex ? 0 : -1}
onClick={() => onChange(i)}
onKeyDown={(e) => handleArrowKeys(e, i, tabs.length, onChange)}
>
{tab.label}
</button>
))}
</div>
{tabs.map((tab, i) => (
<div
key={tab.id}
role="tabpanel"
id={`panel-${tab.id}`}
aria-labelledby={`tab-${tab.id}`}
hidden={i !== activeIndex}
tabIndex={0}
>
{tab.content}
</div>
))}
</div>
);
}
function handleArrowKeys(
event: React.KeyboardEvent,
currentIndex: number,
totalItems: number,
onSelect: (index: number) => void
) {
let newIndex = currentIndex;
switch (event.key) {
case "ArrowRight":
case "ArrowDown":
newIndex = (currentIndex + 1) % totalItems;
break;
case "ArrowLeft":
case "ArrowUp":
newIndex = (currentIndex - 1 + totalItems) % totalItems;
break;
case "Home":
newIndex = 0;
break;
case "End":
newIndex = totalItems - 1;
break;
default:
return;
}
event.preventDefault();
onSelect(newIndex);
}
All interactive elements must be reachable via keyboard. Tab for focus navigation, Enter/Space for activation, Arrow keys for within-component navigation.
function SignupForm() {
return (
<form aria-labelledby="form-title" noValidate>
<h2 id="form-title">Create Account</h2>
<div>
<label htmlFor="email">Email address</label>
<input
id="email"
type="email"
required
aria-required="true"
aria-describedby="email-hint email-error"
aria-invalid={hasError ? "true" : undefined}
/>
<p id="email-hint">We will never share your email.</p>
{hasError && (
<p id="email-error" role="alert">
Please enter a valid email address.
</p>
)}
</div>
<button type="submit">Create Account</button>
</form>
);
}
:root {
--text-primary: #1a1a1a; /* 15.3:1 on white */
--text-secondary: #595959; /* 7.0:1 on white */
--text-on-primary: #ffffff; /* Ensure 4.5:1 on brand color */
--border-focus: #0066cc; /* Visible focus ring */
}
*:focus-visible {
outline: 3px solid var(--border-focus);
outline-offset: 2px;
}
.error-message {
color: #d32f2f;
/* Don't rely on color alone - add icon or text prefix */
}
.error-message::before {
content: "Error: ";
font-weight: bold;
}
WCAG AA requires 4.5:1 contrast for normal text, 3:1 for large text (18px bold or 24px regular).
div and span for clickable elements instead of button or aaria-label when visible text already labels the elementnav, main, article, section)alt text (or alt="" for decorative)<label> elementsrole="alert"