| name | accessibility |
| description | Web accessibility (a11y) best practices and implementation |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"developers","category":"frontend"} |
What I do
- Implement WCAG guidelines
- Create accessible HTML structure
- Handle keyboard navigation
- Implement ARIA attributes
- Ensure proper color contrast
- Support screen readers
- Design for various disabilities
- Test accessibility
When to use me
When creating accessible user interfaces or reviewing for accessibility.
WCAG Guidelines Summary
WCAG 2.1 Principles:
1. Perceivable
- Text alternatives for images
- Captions for audio
- Adaptable layouts
- Distinguishable (color, contrast)
2. Operable
- Keyboard accessible
- Enough time for interactions
- No seizure-inducing content
- Navigable (headings, focus order)
3. Understandable
- Readable and predictable
- Input assistance
4. Robust
- Compatible with current and future user agents
Conformance Levels:
- A: Minimum (required)
- AA: Recommended (target)
- AAA: Highest (exceptional cases)
Semantic HTML
<div class="header">
<div class="logo"></div>
<div class="nav"></div>
</div>
<div class="main">
<div class="article">Content</div>
</div>
<div class="footer"></div>
<header>
<a href="/" aria-label="Homepage">
<img src="logo.png" alt="Company Name">
</a>
<nav aria-label="Main navigation">
<ul>
<li><a =>Products
About
Page Title
Main content goes here...
2024 Company
Keyboard Navigation
<div class="button" onclick="save()">Save</div>
<button type="button" onclick="save()">Save</button>
<head>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
</style>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>...
Main Content
Open Modal
Modal Title
×
Modal content...
ARIA Attributes
<div role="button" tabindex="0" aria-pressed="false">
Toggle Favorite
</div>
<div
role="status"
aria-live="polite"
aria-atomic="true"
>
Changes will be announced
</div>
<form>
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
aria-describedby="email-hint email-error"
aria-invalid="false"
>
<p id="email-hint">We'll never share your email</p>
<p id="email-error" hidden>
Please enter a valid email address
</p>
</form>
Color Contrast
.good-contrast {
color: #1a1a1a;
background-color: #ffffff;
}
.acceptable-contrast {
color: #333333;
background-color: #ffffff;
}
.large-text {
color: #555555;
background-color: #ffffff;
font-size: 18px;
}
.status {
padding: 4px 8px;
border-radius: 4px;
}
.status.success {
background-color: #d4edda;
color: #155724;
}
.status.error {
background-color: #f8d7da;
color: #721c24;
}
.status.success::before {
content: "✓ ";
}
{
: ;
}
Screen Reader Support
<img src="chart.png" alt="Sales chart showing 25% growth in Q4">
<img src="decoration.png" alt="" role="presentation">
<img
src="infographic.png"
alt="Infographic: Step 1 Research, Step 2 Design, Step 3 Development"
>
<details>
<summary>View detailed description</summary>
<p>Full description of the infographic content...</p>
</details>
<table>
<caption>Quarterly Sales Report</caption>
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Revenue</th>
Expenses
Q1
$100,000
$80,000
Form Accessibility
<form>
<fieldset>
<legend>Contact Information</legend>
<div class="form-group">
<label for="name">Full Name <span aria-hidden="true">*</span></label>
<input
type="text"
id="name"
name="name"
required
aria-required="true"
autocomplete="name"
>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
=
>
Phone Number
Include country code (e.g., +1)
Submit
Email
Please enter a valid email address
Testing Accessibility
import pytest
class TestAccessibility:
def test_homepage_accessibility(self, page):
"""Test homepage with axe-core."""
page.goto('/')
violations = page.evaluate('''() => {
const axe = window.axe;
return axe.run(document, {
runOnly: {
type: 'tag',
values: ['wcag2a', 'wcag2aa']
}
});
}''')
assert len(violations['violations']) == 0, \
f"Accessibility violations: {violations['violations']}"
def test_keyboard_navigation(self, page):
"""Test keyboard navigation works."""
page.goto('/')
page.keyboard.press('Tab')
focused = page.evaluate('document.activeElement.tagName')
assert focused in ['A', 'BUTTON', 'INPUT']
def test_color_contrast(self, page):
"""Test color contrast requirements."""
page.goto('/')
violations = page.evaluate('''() => {
const axe = window.axe;
const results = axe.run(document, {
checks: ['color-contrast']
});
return results.violations;
}''')
(violations) ==
ACCESSIBILITY_CHECKLIST =