| name | a11y-annotation-generator |
| description | Adds accessibility annotations (ARIA labels, roles, alt text) to make web content accessible. Use when user asks to "add accessibility", "make accessible", "add aria labels", "wcag compliance", or "screen reader support". |
| allowed-tools | ["Read","Write","Edit","Glob","Grep"] |
Accessibility Annotation Generator
Automatically adds ARIA labels, roles, alt text, and other accessibility annotations to HTML/JSX/Vue templates.
When to Use
- "Make this component accessible"
- "Add ARIA labels"
- "Add alt text to images"
- "Make accessible for screen readers"
- "Add accessibility annotations"
- "WCAG compliance"
Instructions
1. Scan for Accessibility Issues
Analyze HTML/JSX/Vue files for common issues:
grep -r "<img" src/ --include="*.jsx" --include="*.tsx" --include="*.vue" | grep -v "alt="
grep -r "<button" src/ | grep -v "aria-label"
grep -r "<input" src/ | grep -v "aria-label" | grep -v "<label"
grep -r "onClick" src/ | grep -v "role="
2. Add Missing Alt Text
Images:
<img src="/logo.png" />
<img src="/photo.jpg" className="avatar" />
<img src="/logo.png" alt="Company Logo" />
<img src="/photo.jpg" alt="Profile photo of John Doe" className="avatar" />
<img src="/decorative-line.png" alt="" role="presentation" />
Background images (CSS):
<div
className="hero-banner"
role="img"
aria-label="Team collaborating in modern office"
style={{ backgroundImage: 'url(/hero.jpg)' }}
>
3. Add ARIA Labels to Interactive Elements
Buttons:
<button onClick={handleDelete}>
<TrashIcon />
</button>
<button
onClick={handleDelete}
aria-label="Delete item"
>
<TrashIcon aria-hidden="true" />
</button>
<button onClick={handleDelete}>
<TrashIcon aria-hidden="true" />
<span>Delete</span>
</button>
Icon-only buttons:
<button onClick={handleEdit}>
<EditIcon />
</button>
<button
onClick={handleEdit}
aria-label="Edit profile"
title="Edit profile"
>
<EditIcon aria-hidden="true" />
</button>
Links:
<a href="/settings">
<SettingsIcon />
</a>
<a href="/settings" aria-label="Go to settings">
<SettingsIcon aria-hidden="true" />
</a>
<a href="/docs">Click here</a>
<a href="/docs">Read the documentation</a>
4. Add Form Accessibility
Labels for inputs:
<input type="email" placeholder="Email" />
<label htmlFor="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-required="true"
/>
<input
type="email"
aria-label="Email address"
aria-required="true"
placeholder="Email"
/>
Error messages:
{error && <span className="error">{error}</span>}
<input
type="email"
id="email"
aria-invalid={!!error}
aria-describedby={error ? "email-error" : undefined}
/>
{error && (
<span id="email-error" role="alert" className="error">
{error}
</span>
)}
Required fields:
<label htmlFor="name">
Name <span aria-label="required">*</span>
</label>
<input
type="text"
id="name"
required
aria-required="true"
/>
Field descriptions:
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
aria-describedby="password-requirements"
/>
<div id="password-requirements">
Must be at least 8 characters with 1 uppercase letter and 1 number
</div>
5. Add Semantic HTML and Roles
Navigation:
<div className="nav">
<a href="/">Home</a>
<a href="/about">About</a>
</div>
<nav aria-label="Main navigation">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
Landmarks:
<header role="banner">
<nav aria-label="Main navigation">...</nav>
</header>
<main role="main">
<section aria-labelledby="products-heading">
<h2 id="products-heading">Our Products</h2>
...
</section>
</main>
<aside role="complementary" aria-label="Related articles">
...
</aside>
<footer role="contentinfo">
...
</footer>
Lists:
<div className="menu">
<div>Home</div>
<div>About</div>
</div>
<ul role="list">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
6. Add Keyboard Navigation Support
Focusable elements:
<div onClick={handleClick}>Click me</div>
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
handleClick()
}
}}
aria-label="Submit form"
>
Click me
</div>
<button onClick={handleClick} aria-label="Submit form">
Click me
</button>
Skip links:
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<main id="main-content">
...
</main>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Tab order:
<button tabIndex={1}>First</button>
<button tabIndex={2}>Second</button>
<button>First</button>
<button>Second</button>
7. Add Live Regions for Dynamic Content
Announcements:
<div role="status" aria-live="polite">
{statusMessage}
</div>
<div role="alert" aria-live="assertive">
{errorMessage}
</div>
<div
role="status"
aria-live="polite"
aria-busy={isLoading}
>
{isLoading ? 'Loading...' : 'Content loaded'}
</div>
Progress indicators:
<div
role="progressbar"
aria-valuenow={progress}
aria-valuemin={0}
aria-valuemax={100}
aria-label="Upload progress"
>
{progress}%
</div>
8. Add Modal/Dialog Accessibility
Modal:
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<h2 id="modal-title">Confirm Delete</h2>
<p id="modal-description">
Are you sure you want to delete this item?
</p>
<button onClick={handleConfirm}>Confirm</button>
<button onClick={handleCancel}>Cancel</button>
</div>
useEffect(() => {
if (isOpen) {
const previousFocus = document.activeElement
modalRef.current?.focus()
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
handleClose()
}
}
document.addEventListener('keydown', handleKeyDown)
return () => {
document.removeEventListener('keydown', handleKeyDown)
previousFocus?.focus()
}
}
}, [isOpen])
9. Add Dropdown/Menu Accessibility
Dropdown menu:
<div>
<button
aria-expanded={isOpen}
aria-haspopup="true"
aria-controls="dropdown-menu"
onClick={toggleMenu}
>
Menu
</button>
{isOpen && (
<ul
id="dropdown-menu"
role="menu"
>
<li role="menuitem">
<a href="/profile">Profile</a>
</li>
<li role="menuitem">
<a href="/settings">Settings</a>
</li>
<li role="menuitem">
<button onClick={handleLogout}>Logout</button>
</li>
</ul>
)}
</div>
Accordion:
<div>
<h3>
<button
aria-expanded={isExpanded}
aria-controls="panel-1"
id="accordion-header-1"
onClick={toggle}
>
Section 1
</button>
</h3>
{isExpanded && (
<div
id="panel-1"
role="region"
aria-labelledby="accordion-header-1"
>
Content here...
</div>
)}
</div>
10. Add Table Accessibility
Data tables:
<table>
<caption>Employee Information</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Department</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">John Doe</th>
<td>Engineering</td>
<td>john@example.com</td>
</tr>
</tbody>
</table>
Complex tables:
<table>
<thead>
<tr>
<th id="name" scope="col">Name</th>
<th id="monday" scope="col">Monday</th>
<th id="tuesday" scope="col">Tuesday</th>
</tr>
</thead>
<tbody>
<tr>
<th id="john" scope="row">John</th>
<td headers="john monday">9-5</td>
<td headers="john tuesday">9-5</td>
</tr>
</tbody>
</table>
11. Framework-Specific Patterns
React:
import { useEffect, useRef } from 'react'
function AccessibleComponent() {
const headingRef = useRef(null)
useEffect(() => {
headingRef.current?.focus()
document.title = 'New Page Title'
}, [])
return (
<main>
<h1 ref={headingRef} tabIndex={-1}>
Page Title
</h1>
</main>
)
}
Vue:
<template>
<button
@click="handleClick"
:aria-label="buttonLabel"
:aria-pressed="isPressed"
>
<Icon :aria-hidden="true" />
{{ text }}
</button>
</template>
<script>
export default {
computed: {
buttonLabel() {
return `${this.text} (${this.isPressed ? 'active' : 'inactive'})`
}
}
}
</script>
Best Practices
DO:
- Use semantic HTML first (button, not div with onClick)
- Provide visible labels when possible
- Use aria-label only when no visible label
- Test with keyboard only (no mouse)
- Test with screen reader (NVDA, JAWS, VoiceOver)
- Maintain focus order that makes sense
- Announce dynamic changes
DON'T:
- Use positive tabIndex (except -1 for programmatic focus)
- Hide content that should be accessible
- Use color alone to convey information
- Create keyboard traps
- Disable zoom/pinch
- Remove focus outlines without replacement
Testing Checklist