accessibility-patterns
Web accessibility (a11y) patterns for WCAG compliance, ARIA usage, semantic HTML, keyboard navigation, and focus management. Use when building accessible UI, reviewing components for a11y, adding ARIA attributes, handling keyboard interactions, or when the user asks about accessibility, WCAG, screen readers, or focus management.
Works with
---
name: accessibility-patterns
description: Web accessibility (a11y) patterns for WCAG compliance, ARIA usage, semantic HTML, keyboard navigation, and focus management. Use when building accessible UI, reviewing components for a11y, adding ARIA attributes, handling keyboard interactions, or when the user asks about accessibility, WCAG, screen readers, or focus management.
license: MIT
---
# Accessibility Patterns
## Semantic HTML First
Use the correct HTML element before reaching for ARIA. Native elements provide keyboard behavior, focus management, and screen reader semantics for free:
| Need | Use | Not |
| ---------- | ------------------------------------- | ----------------------- |
| Button | `<button>` | `<div onClick>` |
| Link | `<a href>` | `<span onClick>` |
| Navigation | `<nav>` | `<div class="nav">` |
| List | `<ul>` / `<ol>` | `<div>` with divs |
| Heading | `<h1>`–`<h6>` | `<div class="title">` |
| Form field | `<input>` / `<select>` / `<textarea>` | `<div contenteditable>` |
| Dialog | `<dialog>` | `<div class="modal">` |
The first rule of ARIA: don't use ARIA if a native HTML element does the job.
## ARIA Essentials
When native elements aren't sufficient:
### Roles
Add semantic meaning to generic elements:
```html
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
<div role="tabpanel">Content</div>
```
### States and Properties
Communicate dynamic state to assistive technology:
```html
<button aria-expanded="false" aria-controls="menu-content">Menu</button>
<div id="menu-content" hidden>...</div>
<input aria-invalid="true" aria-describedby="error-msg" />
<span id="error-msg">Email is required</span>
```
### Live Regions
Announce dynamic content changes:
```html
<div aria-live="polite" aria-atomic="true">3 results found</div>
<div role="alert">Your session is about to expire.</div>
```
- `aria-live="polite"` — announces after current speech finishes.
- `aria-live="assertive"` or `role="alert"` — interrupts immediately (use sparingly).
### Labels
Every interactive element needs an accessible name:
```html
<!-- Visible label (preferred) -->
<label for="email">Email</label>
<input id="email" type="email" />
<!-- aria-label for icon-only buttons -->
<button aria-label="Close dialog"><CloseIcon /></button>
<!-- aria-labelledby for complex labels -->
<h2 id="section-title">Billing</h2>
<form aria-labelledby="section-title">...</form>
```
## Keyboard Navigation
### Focus Order
- Ensure logical tab order follows visual layout. Don't use `tabindex` > 0.
- Use `tabindex="0"` to make non-interactive elements focusable when necessary.
- Use `tabindex="-1"` to make elements programmatically focusable but not in the tab order.
### Key Handlers
Implement expected keyboard patterns for custom widgets:
| Widget | Keys |
| -------- | -------------------------------------------------------- |
| Button | Enter, Space to activate |
| Menu | Arrow keys to navigate, Enter to select, Escape to close |
| Tabs | Arrow keys to switch, Enter/Space to activate |
| Dialog | Escape to close, trap focus inside |
| Combobox | Arrow keys to navigate, Enter to select, Escape to close |
```tsx
function handleKeyDown(e: React.KeyboardEvent) {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
focusNextItem();
break;
case "ArrowUp":
e.preventDefault();
focusPreviousItem();
break;
case "Escape":
closeMenu();
break;
}
}
```
### Focus Trapping
Modal dialogs must trap focus — Tab and Shift+Tab cycle only through elements inside the dialog:
```tsx
function trapFocus(dialogRef: RefObject<HTMLDialogElement>) {
const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
const first = focusable?.[0];
const last = focusable?.[focusable.length - 1];
function handleTab(e: KeyboardEvent) {
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();
}
}
dialogRef.current?.addEventListener("keydown", handleTab);
}
```
### Focus Restoration
When a dialog/popover closes, return focus to the element that triggered it:
```tsx
const triggerRef = useRef<HTMLButtonElement>(null);
function openDialog() {
triggerRef.current = document.activeElement as HTMLButtonElement;
setOpen(true);
}
function closeDialog() {
setOpen(false);
triggerRef.current?.focus();
}
```
## Color and Contrast
- Minimum contrast ratio: **4.5:1** for normal text, **3:1** for large text (WCAG AA).
- Never rely on color alone to convey meaning — add icons, text, or patterns.
- Test with forced-colors mode (Windows High Contrast).
## Motion
Respect the user's motion preference:
```css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
```tsx
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
```
## Forms
- Associate every input with a `<label>`.
- Group related fields with `<fieldset>` and `<legend>`.
- Display error messages inline, linked with `aria-describedby`.
- Use `aria-required="true"` or the `required` attribute.
- Mark invalid fields with `aria-invalid="true"`.
## Images
```html
<!-- Informative image -->
<img src="chart.png" alt="Sales increased 25% in Q4 2025" />
<!-- Decorative image -->
<img src="divider.svg" alt="" role="presentation" />
<!-- Complex image -->
<figure>
<img src="diagram.png" alt="System architecture overview" />
<figcaption>Request flow from client through API gateway to microservices</figcaption>
</figure>
```
## Testing
- **Keyboard-only**: Tab through the entire page without a mouse.
- **Screen reader**: Test with VoiceOver (macOS), NVDA (Windows), or TalkBack (Android).
- **Automated**: Use `axe-core`, `eslint-plugin-jsx-a11y`, or Lighthouse accessibility audit.
- **Manual**: Zoom to 200%, use forced colors, test with `prefers-reduced-motion`.
- **Testing Library**: Use `getByRole`, `getByLabelText` — if you can't query by role, the component may have an a11y issue.More Accessibility skills
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Also covers Tailwind projects and the STORYBOARD.md / SCRIPT.md plan formats. Read before writing composition HTML.
ui-ux-pro-max
nextlevelbuilder/ui-ux-pro-max-skill
UI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation. Searchable local data: 79 searchable styles (50 active), 192 product palettes and reasoning profiles, 74 font pairings, 119 UX guidelines, 105 icons, 17 GSAP presets, 25 chart types, and 22 stacks.

