Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add soilmass/vibe-coding-plugin --skill "accessibility"
Install specific skill from multi-skill repository
# Description
>
# SKILL.md
name: accessibility
description: >
WCAG 2.1 AA compliance patterns for React 19 β semantic HTML, ARIA attributes, keyboard navigation, focus management, color contrast
allowed-tools: Read, Grep, Glob
Accessibility
Purpose
WCAG 2.1 AA compliance patterns for React 19 + Next.js 15. Covers semantic HTML, ARIA attributes,
keyboard navigation, focus management, and screen reader support. The ONE skill for a11y decisions.
When to Use
- Building interactive components (modals, dropdowns, tabs)
- Adding ARIA attributes to custom UI elements
- Implementing keyboard navigation
- Managing focus for dynamic content
- Auditing components for a11y compliance
When NOT to Use
- Using shadcn/ui components (already accessible) β
shadcn - Form validation patterns β
react-forms - Full site audit β use
a11y-auditoragent instead
Pattern
Semantic HTML over div soup
// CORRECT: semantic elements
<nav aria-label="Main navigation">
<ul>
<li><a href="/about">About</a></li>
<li><a href="/contact" aria-current="page">Contact</a></li>
</ul>
</nav>
// WRONG: div soup
<div className="nav">
<div onClick={() => router.push("/about")}>About</div>
<div onClick={() => router.push("/contact")}>Contact</div>
</div>
Accessible modal β use native <dialog>
"use client";
import { useEffect, useRef } from "react";
export function Modal({ open, onClose, children }: {
open: boolean; onClose: () => void; children: React.ReactNode;
}) {
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (open) dialogRef.current?.showModal();
else dialogRef.current?.close();
}, [open]);
return (
<dialog ref={dialogRef} onClose={onClose} aria-labelledby="modal-title">
<h2 id="modal-title">Title</h2>
{children}
<button onClick={onClose}>Close</button>
</dialog>
);
}
Form errors with aria-describedby
<input id="email" name="email" aria-invalid={!!error} aria-describedby={error ? "email-error" : undefined} />
{error && <p id="email-error" role="alert">{error}</p>}
Skip link + loading state
// layout.tsx: <a href="#main-content" className="sr-only focus:not-sr-only">Skip to content</a>
// SubmitButton: <button aria-busy={pending} disabled={pending}>Submit</button>
Anti-pattern
// WRONG: clickable div without keyboard support
<div onClick={handleClick} className="button">Click me</div>
// CORRECT: use native button element
<button onClick={handleClick}>Click me</button>
// If you MUST use a non-button: add role, tabIndex, onKeyDown, aria-label
Common Mistakes
- Using
divwithonClickinstead of<button>or<a> - Missing
aria-labelon icon-only buttons - Removing focus outlines without providing alternative focus indicators
- Not announcing dynamic content changes to screen readers
- Skipping heading levels (h1 β h3)
- Using
aria-hidden="true"on interactive elements
Checklist
- [ ] Interactive elements use native HTML (
<button>,<a>,<input>) - [ ] Icon-only buttons have
aria-label - [ ] Forms link errors to inputs with
aria-describedby - [ ] Invalid inputs have
aria-invalid - [ ] Modals trap focus and support Escape to close
- [ ] Skip link to main content exists
- [ ] Heading hierarchy is sequential (h1 β h2 β h3)
- [ ] Loading states announced with
aria-busy - [ ] Color is not the sole means of conveying information
Composes With
react-client-componentsβ a11y attributes on interactive client componentsshadcnβ shadcn/ui components are accessible by defaultreact-formsβ form error announcements and validationstorybookβ per-component a11y auditing
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.