Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:20:59 +08:00
commit 468449a68b
5 changed files with 703 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
{
"name": "accessibility-design",
"description": "Accessibility and inclusive design patterns following WCAG 2.2 and ARIA best practices",
"version": "1.0.0",
"author": {
"name": "Brock"
},
"agents": [
"./agents"
],
"commands": [
"./commands"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# accessibility-design
Accessibility and inclusive design patterns following WCAG 2.2 and ARIA best practices

95
agents/a11y-specialist.md Normal file
View File

@@ -0,0 +1,95 @@
# Accessibility Specialist Agent
You are an autonomous agent specialized in implementing accessibility features and inclusive design following WCAG 2.2 guidelines.
## Your Mission
Make applications accessible to all users, including those using assistive technologies, by implementing WCAG 2.2 AA/AAA standards.
## Core Responsibilities
### 1. Audit Accessibility
- Run automated tests (axe, Lighthouse)
- Manual keyboard navigation testing
- Screen reader testing
- Color contrast analysis
- Identify WCAG violations
### 2. Implement Semantic HTML
```html
<header>
<nav aria-label="Main">
<ul><li><a href="/">Home</a></li></ul>
</nav>
</header>
<main>
<article>
<h1>Title</h1>
</article>
</main>
```
### 3. Add ARIA Attributes
```html
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="menu"
>
Menu
</button>
<div
id="menu"
role="menu"
aria-label="Options"
>
<button role="menuitem">Option 1</button>
</div>
```
### 4. Implement Keyboard Navigation
- All interactive elements accessible via Tab
- Arrow keys for menus/lists
- Escape to close modals/dropdowns
- Enter/Space to activate
- Focus management
### 5. Ensure Color Contrast
- 4.5:1 minimum for normal text (AA)
- 3:1 for large text (AA)
- 7:1 for enhanced contrast (AAA)
- Don't rely on color alone
### 6. Add Alternative Text
```html
<img src="chart.png" alt="Sales increased 25% in Q4" />
<img src="decorative.png" alt="" role="presentation" />
```
### 7. Test with Assistive Tech
- Screen readers (NVDA, JAWS, VoiceOver)
- Keyboard only
- Voice control
- Screen magnification
## Best Practices
- Use semantic HTML
- Provide text alternatives
- Ensure keyboard access
- Maintain color contrast
- Logical heading structure
- Proper form labels
- Visible focus indicators
- Support screen readers
- Test thoroughly
## Deliverables
1. Accessibility audit report
2. WCAG compliant implementation
3. ARIA annotations
4. Keyboard navigation
5. Testing documentation
6. Remediation plan

542
commands/a11y-patterns.md Normal file
View File

@@ -0,0 +1,542 @@
# Accessibility & Inclusive Design Patterns
Comprehensive accessibility patterns following WCAG 2.2 guidelines and ARIA best practices.
## WCAG 2.2 Principles (POUR)
### Perceivable
Information and UI components must be presentable to users in ways they can perceive.
### Operable
UI components and navigation must be operable.
### Understandable
Information and operation of UI must be understandable.
### Robust
Content must be robust enough to be interpreted by various user agents, including assistive technologies.
## Semantic HTML
```html
<!-- Good: Semantic structure -->
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Page Title</h1>
<p>Content...</p>
</article>
<aside aria-label="Related content">
<h2>Related Articles</h2>
</aside>
</main>
<footer>
<p>&copy; 2024 Company Name</p>
</footer>
<!-- Bad: Non-semantic -->
<div class="header">
<div class="nav">
<div class="link">Home</div>
</div>
</div>
```
## ARIA Attributes
### Landmarks
```html
<nav aria-label="Primary navigation">
<main role="main">
<aside aria-label="Sidebar">
<footer role="contentinfo">
```
### Live Regions
```html
<!-- Announce changes immediately -->
<div role="alert" aria-live="assertive">
Error: Please correct the form errors.
</div>
<!-- Announce when convenient -->
<div aria-live="polite" aria-atomic="true">
5 new messages
</div>
<!-- Status messages -->
<div role="status" aria-live="polite">
Item added to cart
</div>
```
### Form Accessibility
```html
<!-- Proper labeling -->
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-describedby="email-error email-help"
/>
<span id="email-help">We'll never share your email</span>
<span id="email-error" role="alert" aria-live="polite">
<!-- Error message appears here -->
</span>
<!-- Grouped inputs -->
<fieldset>
<legend>Contact Preferences</legend>
<div>
<input type="checkbox" id="email-pref" name="contact" value="email" />
<label for="email-pref">Email</label>
</div>
<div>
<input type="checkbox" id="phone-pref" name="contact" value="phone" />
<label for="phone-pref">Phone</label>
</div>
</fieldset>
<!-- Radio groups -->
<fieldset>
<legend>Subscription Type</legend>
<div>
<input type="radio" id="free" name="subscription" value="free" checked />
<label for="free">Free</label>
</div>
<div>
<input type="radio" id="premium" name="subscription" value="premium" />
<label for="premium">Premium</label>
</div>
</fieldset>
```
## Keyboard Navigation
```typescript
// React component with keyboard support
function DropdownMenu({ items }) {
const [isOpen, setIsOpen] = useState(false);
const [focusedIndex, setFocusedIndex] = useState(0);
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
setIsOpen(!isOpen);
break;
case 'Escape':
setIsOpen(false);
buttonRef.current?.focus();
break;
case 'ArrowDown':
e.preventDefault();
if (!isOpen) {
setIsOpen(true);
} else {
setFocusedIndex((prev) => (prev + 1) % items.length);
}
break;
case 'ArrowUp':
e.preventDefault();
if (isOpen) {
setFocusedIndex((prev) => (prev - 1 + items.length) % items.length);
}
break;
case 'Home':
e.preventDefault();
setFocusedIndex(0);
break;
case 'End':
e.preventDefault();
setFocusedIndex(items.length - 1);
break;
}
};
return (
<div onKeyDown={handleKeyDown}>
<button
ref={buttonRef}
aria-haspopup="true"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
Menu
</button>
{isOpen && (
<div
ref={menuRef}
role="menu"
aria-orientation="vertical"
>
{items.map((item, index) => (
<button
key={item.id}
role="menuitem"
tabIndex={index === focusedIndex ? 0 : -1}
onClick={() => item.onClick()}
>
{item.label}
</button>
))}
</div>
)}
</div>
);
}
```
## Focus Management
```typescript
// Trap focus in modal
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen) return;
const modal = modalRef.current;
if (!modal) return;
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
// Focus first element
firstElement?.focus();
const handleTabKey = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement?.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement?.focus();
}
}
};
modal.addEventListener('keydown', handleTabKey);
return () => {
modal.removeEventListener('keydown', handleTabKey);
};
}, [isOpen]);
if (!isOpen) return null;
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Modal Title</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
);
}
```
## Color Contrast
```css
/* WCAG AA requires 4.5:1 for normal text, 3:1 for large text */
/* WCAG AAA requires 7:1 for normal text, 4.5:1 for large text */
.button-primary {
/* Good contrast: 7.2:1 */
background: #0066cc;
color: #ffffff;
}
.button-secondary {
/* Good contrast: 4.6:1 */
background: #f0f0f0;
color: #333333;
}
/* Don't rely on color alone */
.error {
color: #cc0000;
}
.error::before {
content: '⚠ '; /* Visual indicator */
}
.error[aria-invalid="true"] {
/* Also announced to screen readers */
}
```
## Skip Links
```html
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
</style>
```
## Images and Alt Text
```html
<!-- Informative image -->
<img src="chart.png" alt="Sales increased by 25% in Q4 2024" />
<!-- Decorative image -->
<img src="decorative.png" alt="" role="presentation" />
<!-- Complex image -->
<figure>
<img src="complex-chart.png" alt="Detailed sales data" />
<figcaption>
<details>
<summary>Chart description</summary>
<p>Detailed text description of the chart data...</p>
</details>
</figcaption>
</figure>
<!-- Icon with text -->
<button>
<svg aria-hidden="true"><path d="..." /></svg>
<span>Delete</span>
</button>
<!-- Icon without text -->
<button aria-label="Delete item">
<svg aria-hidden="true"><path d="..." /></svg>
</button>
```
## Screen Reader-Only Content
```css
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
white-space: normal;
}
```
```html
<button>
<svg aria-hidden="true">...</svg>
<span class="sr-only">Settings</span>
</button>
```
## Responsive and Touch-Friendly
```css
/* Minimum touch target size: 44x44px */
button,
a {
min-height: 44px;
min-width: 44px;
padding: 12px 24px;
}
/* Ensure adequate spacing */
.button-group button {
margin: 4px;
}
```
## Headings Hierarchy
```html
<!-- Good: Logical hierarchy -->
<h1>Page Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h3>Subsection 1.2</h3>
<h2>Section 2</h2>
<!-- Bad: Skipped levels -->
<h1>Page Title</h1>
<h3>Section</h3> <!-- Skipped h2 -->
```
## Tables
```html
<table>
<caption>Monthly Sales Report</caption>
<thead>
<tr>
<th scope="col">Month</th>
<th scope="col">Sales</th>
<th scope="col">Growth</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">January</th>
<td>$10,000</td>
<td>+5%</td>
</tr>
</tbody>
</table>
```
## Loading States
```typescript
function DataList() {
const { data, isLoading } = useQuery('data');
if (isLoading) {
return (
<div role="status" aria-live="polite" aria-busy="true">
<span className="sr-only">Loading data...</span>
<Spinner aria-hidden="true" />
</div>
);
}
return (
<div role="region" aria-label="Data list">
{data.map(item => <Item key={item.id} {...item} />)}
</div>
);
}
```
## Testing Accessibility
### Automated Testing
```typescript
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('page has no accessibility violations', async () => {
const { container } = render(<App />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
```
### Manual Testing Checklist
- [ ] Keyboard navigation works
- [ ] Screen reader announces correctly
- [ ] Color contrast meets WCAG AA
- [ ] Forms have proper labels
- [ ] Images have alt text
- [ ] Headings are hierarchical
- [ ] Focus indicators are visible
- [ ] Touch targets are adequate
- [ ] No color-only information
- [ ] Text can be resized to 200%
## Reduced Motion
```css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
```typescript
function AnimatedComponent() {
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return (
<motion.div
animate={{ opacity: 1 }}
transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
>
Content
</motion.div>
);
}
```
## Best Practices
1. **Use semantic HTML**
2. **Provide text alternatives**
3. **Ensure keyboard accessibility**
4. **Maintain sufficient color contrast**
5. **Create logical heading structure**
6. **Label form inputs properly**
7. **Make focus indicators visible**
8. **Support screen readers**
9. **Test with real assistive technologies**
10. **Follow WCAG 2.2 guidelines**

49
plugin.lock.json Normal file
View File

@@ -0,0 +1,49 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:Dieshen/claude_marketplace:plugins/accessibility-design",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "59a790231bd3248131fc8735a376a95803384969",
"treeHash": "d7b7f73e436ef8af7b8df1299a323dc1ca76fa5edd653133bb825629ad4367e7",
"generatedAt": "2025-11-28T10:10:24.637350Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "accessibility-design",
"description": "Accessibility and inclusive design patterns following WCAG 2.2 and ARIA best practices",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "f3460be179d27f5dc51986dacd6d8b1a71a2a11b667dd484b0eede3d0fcee0bb"
},
{
"path": "agents/a11y-specialist.md",
"sha256": "ac9271aeae54c1d4ce843733bedbd1b9aae6d51f81f427bbd049716706d80af5"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "1c2d6d6a981e899a1f1b9f61d91334711480d49e7a5b7ba8bfdc8bde60cfc3d9"
},
{
"path": "commands/a11y-patterns.md",
"sha256": "d18058d0271a3c6f4012fd692815c1fcba5736bd478f4b0652a078d208c16cc2"
}
],
"dirSha256": "d7b7f73e436ef8af7b8df1299a323dc1ca76fa5edd653133bb825629ad4367e7"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}