mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 09:37:15 +00:00
feat: add Conductor plugin for Context-Driven Development
Add comprehensive Conductor plugin implementing Context-Driven Development methodology with tracks, specs, and phased implementation plans. Components: - 5 commands: setup, new-track, implement, status, revert - 1 agent: conductor-validator - 3 skills: context-driven-development, track-management, workflow-patterns - 18 templates for project artifacts Documentation updates: - README.md: Updated counts (68 plugins, 100 agents, 110 skills, 76 tools) - docs/plugins.md: Added Conductor to Workflows section - docs/agents.md: Added conductor-validator agent - docs/agent-skills.md: Added Conductor skills section Also includes Prettier formatting across all project files.
This commit is contained in:
@@ -20,13 +20,13 @@ Practical guide to testing web applications with screen readers for comprehensiv
|
||||
|
||||
### 1. Major Screen Readers
|
||||
|
||||
| Screen Reader | Platform | Browser | Usage |
|
||||
|---------------|----------|---------|-------|
|
||||
| **VoiceOver** | macOS/iOS | Safari | ~15% |
|
||||
| **NVDA** | Windows | Firefox/Chrome | ~31% |
|
||||
| **JAWS** | Windows | Chrome/IE | ~40% |
|
||||
| **TalkBack** | Android | Chrome | ~10% |
|
||||
| **Narrator** | Windows | Edge | ~4% |
|
||||
| Screen Reader | Platform | Browser | Usage |
|
||||
| ------------- | --------- | -------------- | ----- |
|
||||
| **VoiceOver** | macOS/iOS | Safari | ~15% |
|
||||
| **NVDA** | Windows | Firefox/Chrome | ~31% |
|
||||
| **JAWS** | Windows | Chrome/IE | ~40% |
|
||||
| **TalkBack** | Android | Chrome | ~10% |
|
||||
| **Narrator** | Windows | Edge | ~4% |
|
||||
|
||||
### 2. Testing Priority
|
||||
|
||||
@@ -44,11 +44,11 @@ Comprehensive Coverage:
|
||||
|
||||
### 3. Screen Reader Modes
|
||||
|
||||
| Mode | Purpose | When Used |
|
||||
|------|---------|-----------|
|
||||
| **Browse/Virtual** | Read content | Default reading |
|
||||
| **Focus/Forms** | Interact with controls | Filling forms |
|
||||
| **Application** | Custom widgets | ARIA applications |
|
||||
| Mode | Purpose | When Used |
|
||||
| ------------------ | ---------------------- | ----------------- |
|
||||
| **Browse/Virtual** | Read content | Default reading |
|
||||
| **Focus/Forms** | Interact with controls | Filling forms |
|
||||
| **Application** | Custom widgets | ARIA applications |
|
||||
|
||||
## VoiceOver (macOS)
|
||||
|
||||
@@ -101,22 +101,26 @@ VO + Cmd + T Next table
|
||||
## VoiceOver Testing Checklist
|
||||
|
||||
### Page Load
|
||||
|
||||
- [ ] Page title announced
|
||||
- [ ] Main landmark found
|
||||
- [ ] Skip link works
|
||||
|
||||
### Navigation
|
||||
|
||||
- [ ] All headings discoverable via rotor
|
||||
- [ ] Heading levels logical (H1 → H2 → H3)
|
||||
- [ ] Landmarks properly labeled
|
||||
- [ ] Skip links functional
|
||||
|
||||
### Links & Buttons
|
||||
|
||||
- [ ] Link purpose clear
|
||||
- [ ] Button actions described
|
||||
- [ ] New window/tab announced
|
||||
|
||||
### Forms
|
||||
|
||||
- [ ] All labels read with inputs
|
||||
- [ ] Required fields announced
|
||||
- [ ] Error messages read
|
||||
@@ -124,12 +128,14 @@ VO + Cmd + T Next table
|
||||
- [ ] Focus moves to errors
|
||||
|
||||
### Dynamic Content
|
||||
|
||||
- [ ] Alerts announced immediately
|
||||
- [ ] Loading states communicated
|
||||
- [ ] Content updates announced
|
||||
- [ ] Modals trap focus correctly
|
||||
|
||||
### Tables
|
||||
|
||||
- [ ] Headers associated with cells
|
||||
- [ ] Table navigation works
|
||||
- [ ] Complex tables have captions
|
||||
@@ -151,11 +157,11 @@ VO + Cmd + T Next table
|
||||
<div id="results" role="status" aria-live="polite">New results loaded</div>
|
||||
|
||||
<!-- Issue: Form error not read -->
|
||||
<input type="email">
|
||||
<input type="email" />
|
||||
<span class="error">Invalid email</span>
|
||||
|
||||
<!-- Fix -->
|
||||
<input type="email" aria-invalid="true" aria-describedby="email-error">
|
||||
<input type="email" aria-invalid="true" aria-describedby="email-error" />
|
||||
<span id="email-error" role="alert">Invalid email</span>
|
||||
```
|
||||
|
||||
@@ -235,23 +241,27 @@ Watch for:
|
||||
## NVDA Test Script
|
||||
|
||||
### Initial Load
|
||||
|
||||
1. Navigate to page
|
||||
2. Let page finish loading
|
||||
3. Press Insert + Down to read all
|
||||
4. Note: Page title, main content identified?
|
||||
|
||||
### Landmark Navigation
|
||||
|
||||
1. Press D repeatedly
|
||||
2. Check: All main areas reachable?
|
||||
3. Check: Landmarks properly labeled?
|
||||
|
||||
### Heading Navigation
|
||||
|
||||
1. Press Insert + F7 → Headings
|
||||
2. Check: Logical heading structure?
|
||||
3. Press H to navigate headings
|
||||
4. Check: All sections discoverable?
|
||||
|
||||
### Form Testing
|
||||
|
||||
1. Press F to find first form field
|
||||
2. Check: Label read?
|
||||
3. Fill in invalid data
|
||||
@@ -260,12 +270,14 @@ Watch for:
|
||||
6. Check: Focus moved to error?
|
||||
|
||||
### Interactive Elements
|
||||
|
||||
1. Tab through all interactive elements
|
||||
2. Check: Each announces role and state
|
||||
3. Activate buttons with Enter/Space
|
||||
4. Check: Result announced?
|
||||
|
||||
### Dynamic Content
|
||||
|
||||
1. Trigger content update
|
||||
2. Check: Change announced?
|
||||
3. Open modal
|
||||
@@ -345,10 +357,12 @@ Reading Controls (swipe up then right):
|
||||
|
||||
```html
|
||||
<!-- Accessible modal structure -->
|
||||
<div role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="dialog-title"
|
||||
aria-describedby="dialog-desc">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="dialog-title"
|
||||
aria-describedby="dialog-desc"
|
||||
>
|
||||
<h2 id="dialog-title">Confirm Delete</h2>
|
||||
<p id="dialog-desc">This action cannot be undone.</p>
|
||||
<button>Cancel</button>
|
||||
@@ -363,10 +377,10 @@ function openModal(modal) {
|
||||
lastFocus = document.activeElement;
|
||||
|
||||
// Move focus to modal
|
||||
modal.querySelector('h2').focus();
|
||||
modal.querySelector("h2").focus();
|
||||
|
||||
// Trap focus
|
||||
modal.addEventListener('keydown', trapFocus);
|
||||
modal.addEventListener("keydown", trapFocus);
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
@@ -375,9 +389,9 @@ function closeModal(modal) {
|
||||
}
|
||||
|
||||
function trapFocus(e) {
|
||||
if (e.key === 'Tab') {
|
||||
if (e.key === "Tab") {
|
||||
const focusable = modal.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
||||
);
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
@@ -391,7 +405,7 @@ function trapFocus(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (e.key === "Escape") {
|
||||
closeModal(modal);
|
||||
}
|
||||
}
|
||||
@@ -411,12 +425,13 @@ function trapFocus(e) {
|
||||
</div>
|
||||
|
||||
<!-- Progress updates -->
|
||||
<div role="progressbar"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Upload progress">
|
||||
</div>
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Upload progress"
|
||||
></div>
|
||||
|
||||
<!-- Log (additions only) -->
|
||||
<div role="log" aria-live="polite" aria-relevant="additions">
|
||||
@@ -428,53 +443,47 @@ function trapFocus(e) {
|
||||
|
||||
```html
|
||||
<div role="tablist" aria-label="Product information">
|
||||
<button role="tab"
|
||||
id="tab-1"
|
||||
aria-selected="true"
|
||||
aria-controls="panel-1">
|
||||
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
|
||||
Description
|
||||
</button>
|
||||
<button role="tab"
|
||||
id="tab-2"
|
||||
aria-selected="false"
|
||||
aria-controls="panel-2"
|
||||
tabindex="-1">
|
||||
<button
|
||||
role="tab"
|
||||
id="tab-2"
|
||||
aria-selected="false"
|
||||
aria-controls="panel-2"
|
||||
tabindex="-1"
|
||||
>
|
||||
Reviews
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel"
|
||||
id="panel-1"
|
||||
aria-labelledby="tab-1">
|
||||
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
|
||||
Product description content...
|
||||
</div>
|
||||
|
||||
<div role="tabpanel"
|
||||
id="panel-2"
|
||||
aria-labelledby="tab-2"
|
||||
hidden>
|
||||
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
|
||||
Reviews content...
|
||||
</div>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Tab keyboard navigation
|
||||
tablist.addEventListener('keydown', (e) => {
|
||||
tablist.addEventListener("keydown", (e) => {
|
||||
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
|
||||
const index = tabs.indexOf(document.activeElement);
|
||||
|
||||
let newIndex;
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
case "ArrowRight":
|
||||
newIndex = (index + 1) % tabs.length;
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
case "ArrowLeft":
|
||||
newIndex = (index - 1 + tabs.length) % tabs.length;
|
||||
break;
|
||||
case 'Home':
|
||||
case "Home":
|
||||
newIndex = 0;
|
||||
break;
|
||||
case 'End':
|
||||
case "End":
|
||||
newIndex = tabs.length - 1;
|
||||
break;
|
||||
default:
|
||||
@@ -494,17 +503,18 @@ tablist.addEventListener('keydown', (e) => {
|
||||
function logAccessibleName(element) {
|
||||
const computed = window.getComputedStyle(element);
|
||||
console.log({
|
||||
role: element.getAttribute('role') || element.tagName,
|
||||
name: element.getAttribute('aria-label') ||
|
||||
element.getAttribute('aria-labelledby') ||
|
||||
element.textContent,
|
||||
role: element.getAttribute("role") || element.tagName,
|
||||
name:
|
||||
element.getAttribute("aria-label") ||
|
||||
element.getAttribute("aria-labelledby") ||
|
||||
element.textContent,
|
||||
state: {
|
||||
expanded: element.getAttribute('aria-expanded'),
|
||||
selected: element.getAttribute('aria-selected'),
|
||||
checked: element.getAttribute('aria-checked'),
|
||||
disabled: element.disabled
|
||||
expanded: element.getAttribute("aria-expanded"),
|
||||
selected: element.getAttribute("aria-selected"),
|
||||
checked: element.getAttribute("aria-checked"),
|
||||
disabled: element.disabled,
|
||||
},
|
||||
visible: computed.display !== 'none' && computed.visibility !== 'hidden'
|
||||
visible: computed.display !== "none" && computed.visibility !== "hidden",
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -512,6 +522,7 @@ function logAccessibleName(element) {
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Test with actual screen readers** - Not just simulators
|
||||
- **Use semantic HTML first** - ARIA is supplemental
|
||||
- **Test in browse and focus modes** - Different experiences
|
||||
@@ -519,6 +530,7 @@ function logAccessibleName(element) {
|
||||
- **Test keyboard only first** - Foundation for SR testing
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't assume one SR is enough** - Test multiple
|
||||
- **Don't ignore mobile** - Growing user base
|
||||
- **Don't test only happy path** - Test error states
|
||||
|
||||
@@ -20,10 +20,10 @@ Comprehensive guide to auditing web content against WCAG 2.2 guidelines with act
|
||||
|
||||
### 1. WCAG Conformance Levels
|
||||
|
||||
| Level | Description | Required For |
|
||||
|-------|-------------|--------------|
|
||||
| **A** | Minimum accessibility | Legal baseline |
|
||||
| **AA** | Standard conformance | Most regulations |
|
||||
| Level | Description | Required For |
|
||||
| ------- | ---------------------- | ----------------- |
|
||||
| **A** | Minimum accessibility | Legal baseline |
|
||||
| **AA** | Standard conformance | Most regulations |
|
||||
| **AAA** | Enhanced accessibility | Specialized needs |
|
||||
|
||||
### 2. POUR Principles
|
||||
@@ -61,10 +61,11 @@ Moderate:
|
||||
|
||||
### Perceivable (Principle 1)
|
||||
|
||||
```markdown
|
||||
````markdown
|
||||
## 1.1 Text Alternatives
|
||||
|
||||
### 1.1.1 Non-text Content (Level A)
|
||||
|
||||
- [ ] All images have alt text
|
||||
- [ ] Decorative images have alt=""
|
||||
- [ ] Complex images have long descriptions
|
||||
@@ -72,33 +73,39 @@ Moderate:
|
||||
- [ ] CAPTCHAs have alternatives
|
||||
|
||||
Check:
|
||||
|
||||
```html
|
||||
<!-- Good -->
|
||||
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2">
|
||||
<img src="decorative-line.png" alt="">
|
||||
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2" />
|
||||
<img src="decorative-line.png" alt="" />
|
||||
|
||||
<!-- Bad -->
|
||||
<img src="chart.png">
|
||||
<img src="decorative-line.png" alt="decorative line">
|
||||
<img src="chart.png" />
|
||||
<img src="decorative-line.png" alt="decorative line" />
|
||||
```
|
||||
````
|
||||
|
||||
## 1.2 Time-based Media
|
||||
|
||||
### 1.2.1 Audio-only and Video-only (Level A)
|
||||
|
||||
- [ ] Audio has text transcript
|
||||
- [ ] Video has audio description or transcript
|
||||
|
||||
### 1.2.2 Captions (Level A)
|
||||
|
||||
- [ ] All video has synchronized captions
|
||||
- [ ] Captions are accurate and complete
|
||||
- [ ] Speaker identification included
|
||||
|
||||
### 1.2.3 Audio Description (Level A)
|
||||
|
||||
- [ ] Video has audio description for visual content
|
||||
|
||||
## 1.3 Adaptable
|
||||
|
||||
### 1.3.1 Info and Relationships (Level A)
|
||||
|
||||
- [ ] Headings use proper tags (h1-h6)
|
||||
- [ ] Lists use ul/ol/dl
|
||||
- [ ] Tables have headers
|
||||
@@ -106,38 +113,46 @@ Check:
|
||||
- [ ] ARIA landmarks present
|
||||
|
||||
Check:
|
||||
|
||||
```html
|
||||
<!-- Heading hierarchy -->
|
||||
<h1>Page Title</h1>
|
||||
<h2>Section</h2>
|
||||
<h3>Subsection</h3>
|
||||
<h2>Another Section</h2>
|
||||
<h2>Section</h2>
|
||||
<h3>Subsection</h3>
|
||||
<h2>Another Section</h2>
|
||||
|
||||
<!-- Table headers -->
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th scope="col">Name</th><th scope="col">Price</th></tr>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
```
|
||||
|
||||
### 1.3.2 Meaningful Sequence (Level A)
|
||||
|
||||
- [ ] Reading order is logical
|
||||
- [ ] CSS positioning doesn't break order
|
||||
- [ ] Focus order matches visual order
|
||||
|
||||
### 1.3.3 Sensory Characteristics (Level A)
|
||||
|
||||
- [ ] Instructions don't rely on shape/color alone
|
||||
- [ ] "Click the red button" → "Click Submit (red button)"
|
||||
|
||||
## 1.4 Distinguishable
|
||||
|
||||
### 1.4.1 Use of Color (Level A)
|
||||
|
||||
- [ ] Color is not only means of conveying info
|
||||
- [ ] Links distinguishable without color
|
||||
- [ ] Error states not color-only
|
||||
|
||||
### 1.4.3 Contrast (Minimum) (Level AA)
|
||||
|
||||
- [ ] Text: 4.5:1 contrast ratio
|
||||
- [ ] Large text (18pt+): 3:1 ratio
|
||||
- [ ] UI components: 3:1 ratio
|
||||
@@ -145,27 +160,32 @@ Check:
|
||||
Tools: WebAIM Contrast Checker, axe DevTools
|
||||
|
||||
### 1.4.4 Resize Text (Level AA)
|
||||
|
||||
- [ ] Text resizes to 200% without loss
|
||||
- [ ] No horizontal scrolling at 320px
|
||||
- [ ] Content reflows properly
|
||||
|
||||
### 1.4.10 Reflow (Level AA)
|
||||
|
||||
- [ ] Content reflows at 400% zoom
|
||||
- [ ] No two-dimensional scrolling
|
||||
- [ ] All content accessible at 320px width
|
||||
|
||||
### 1.4.11 Non-text Contrast (Level AA)
|
||||
|
||||
- [ ] UI components have 3:1 contrast
|
||||
- [ ] Focus indicators visible
|
||||
- [ ] Graphical objects distinguishable
|
||||
|
||||
### 1.4.12 Text Spacing (Level AA)
|
||||
|
||||
- [ ] No content loss with increased spacing
|
||||
- [ ] Line height 1.5x font size
|
||||
- [ ] Paragraph spacing 2x font size
|
||||
- [ ] Letter spacing 0.12x font size
|
||||
- [ ] Word spacing 0.16x font size
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
### Operable (Principle 2)
|
||||
|
||||
@@ -183,9 +203,10 @@ Check:
|
||||
// Custom button must be keyboard accessible
|
||||
<div role="button" tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') activate()">
|
||||
```
|
||||
````
|
||||
|
||||
### 2.1.2 No Keyboard Trap (Level A)
|
||||
|
||||
- [ ] Focus can move away from all components
|
||||
- [ ] Modal dialogs trap focus correctly
|
||||
- [ ] Focus returns after modal closes
|
||||
@@ -193,11 +214,13 @@ Check:
|
||||
## 2.2 Enough Time
|
||||
|
||||
### 2.2.1 Timing Adjustable (Level A)
|
||||
|
||||
- [ ] Session timeouts can be extended
|
||||
- [ ] User warned before timeout
|
||||
- [ ] Option to disable auto-refresh
|
||||
|
||||
### 2.2.2 Pause, Stop, Hide (Level A)
|
||||
|
||||
- [ ] Moving content can be paused
|
||||
- [ ] Auto-updating content can be paused
|
||||
- [ ] Animations respect prefers-reduced-motion
|
||||
@@ -214,12 +237,14 @@ Check:
|
||||
## 2.3 Seizures and Physical Reactions
|
||||
|
||||
### 2.3.1 Three Flashes (Level A)
|
||||
|
||||
- [ ] No content flashes more than 3 times/second
|
||||
- [ ] Flashing area is small (<25% viewport)
|
||||
|
||||
## 2.4 Navigable
|
||||
|
||||
### 2.4.1 Bypass Blocks (Level A)
|
||||
|
||||
- [ ] Skip to main content link present
|
||||
- [ ] Landmark regions defined
|
||||
- [ ] Proper heading structure
|
||||
@@ -230,14 +255,17 @@ Check:
|
||||
```
|
||||
|
||||
### 2.4.2 Page Titled (Level A)
|
||||
|
||||
- [ ] Unique, descriptive page titles
|
||||
- [ ] Title reflects page content
|
||||
|
||||
### 2.4.3 Focus Order (Level A)
|
||||
|
||||
- [ ] Focus order matches visual order
|
||||
- [ ] tabindex used correctly
|
||||
|
||||
### 2.4.4 Link Purpose (In Context) (Level A)
|
||||
|
||||
- [ ] Links make sense out of context
|
||||
- [ ] No "click here" or "read more" alone
|
||||
|
||||
@@ -250,10 +278,12 @@ Check:
|
||||
```
|
||||
|
||||
### 2.4.6 Headings and Labels (Level AA)
|
||||
|
||||
- [ ] Headings describe content
|
||||
- [ ] Labels describe purpose
|
||||
|
||||
### 2.4.7 Focus Visible (Level AA)
|
||||
|
||||
- [ ] Focus indicator visible on all elements
|
||||
- [ ] Custom focus styles meet contrast
|
||||
|
||||
@@ -265,9 +295,11 @@ Check:
|
||||
```
|
||||
|
||||
### 2.4.11 Focus Not Obscured (Level AA) - WCAG 2.2
|
||||
|
||||
- [ ] Focused element not fully hidden
|
||||
- [ ] Sticky headers don't obscure focus
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
### Understandable (Principle 3)
|
||||
|
||||
@@ -280,10 +312,12 @@ Check:
|
||||
|
||||
```html
|
||||
<html lang="en">
|
||||
```
|
||||
````
|
||||
|
||||
### 3.1.2 Language of Parts (Level AA)
|
||||
|
||||
- [ ] Language changes marked
|
||||
|
||||
```html
|
||||
<p>The French word <span lang="fr">bonjour</span> means hello.</p>
|
||||
```
|
||||
@@ -291,47 +325,56 @@ Check:
|
||||
## 3.2 Predictable
|
||||
|
||||
### 3.2.1 On Focus (Level A)
|
||||
|
||||
- [ ] No context change on focus alone
|
||||
- [ ] No unexpected popups on focus
|
||||
|
||||
### 3.2.2 On Input (Level A)
|
||||
|
||||
- [ ] No automatic form submission
|
||||
- [ ] User warned before context change
|
||||
|
||||
### 3.2.3 Consistent Navigation (Level AA)
|
||||
|
||||
- [ ] Navigation consistent across pages
|
||||
- [ ] Repeated components same order
|
||||
|
||||
### 3.2.4 Consistent Identification (Level AA)
|
||||
|
||||
- [ ] Same functionality = same label
|
||||
- [ ] Icons used consistently
|
||||
|
||||
## 3.3 Input Assistance
|
||||
|
||||
### 3.3.1 Error Identification (Level A)
|
||||
|
||||
- [ ] Errors clearly identified
|
||||
- [ ] Error message describes problem
|
||||
- [ ] Error linked to field
|
||||
|
||||
```html
|
||||
<input aria-describedby="email-error" aria-invalid="true">
|
||||
<input aria-describedby="email-error" aria-invalid="true" />
|
||||
<span id="email-error" role="alert">Please enter valid email</span>
|
||||
```
|
||||
|
||||
### 3.3.2 Labels or Instructions (Level A)
|
||||
|
||||
- [ ] All inputs have visible labels
|
||||
- [ ] Required fields indicated
|
||||
- [ ] Format hints provided
|
||||
|
||||
### 3.3.3 Error Suggestion (Level AA)
|
||||
|
||||
- [ ] Errors include correction suggestion
|
||||
- [ ] Suggestions are specific
|
||||
|
||||
### 3.3.4 Error Prevention (Level AA)
|
||||
|
||||
- [ ] Legal/financial forms reversible
|
||||
- [ ] Data checked before submission
|
||||
- [ ] User can review before submit
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
### Robust (Principle 4)
|
||||
|
||||
@@ -356,23 +399,21 @@ Check:
|
||||
aria-labelledby="label">
|
||||
</div>
|
||||
<span id="label">Accept terms</span>
|
||||
```
|
||||
````
|
||||
|
||||
### 4.1.3 Status Messages (Level AA)
|
||||
|
||||
- [ ] Status updates announced
|
||||
- [ ] Live regions used correctly
|
||||
|
||||
```html
|
||||
<div role="status" aria-live="polite">
|
||||
3 items added to cart
|
||||
</div>
|
||||
<div role="status" aria-live="polite">3 items added to cart</div>
|
||||
|
||||
<div role="alert" aria-live="assertive">
|
||||
Error: Form submission failed
|
||||
</div>
|
||||
```
|
||||
<div role="alert" aria-live="assertive">Error: Form submission failed</div>
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
## Automated Testing
|
||||
|
||||
```javascript
|
||||
@@ -405,7 +446,7 @@ test('should have no accessibility violations', async ({ page }) => {
|
||||
|
||||
expect(results.violations).toHaveLength(0);
|
||||
});
|
||||
```
|
||||
````
|
||||
|
||||
```bash
|
||||
# CLI tools
|
||||
@@ -420,28 +461,32 @@ lighthouse https://example.com --only-categories=accessibility
|
||||
|
||||
```html
|
||||
<!-- Before -->
|
||||
<input type="email" placeholder="Email">
|
||||
<input type="email" placeholder="Email" />
|
||||
|
||||
<!-- After: Option 1 - Visible label -->
|
||||
<label for="email">Email address</label>
|
||||
<input id="email" type="email">
|
||||
<input id="email" type="email" />
|
||||
|
||||
<!-- After: Option 2 - aria-label -->
|
||||
<input type="email" aria-label="Email address">
|
||||
<input type="email" aria-label="Email address" />
|
||||
|
||||
<!-- After: Option 3 - aria-labelledby -->
|
||||
<span id="email-label">Email</span>
|
||||
<input type="email" aria-labelledby="email-label">
|
||||
<input type="email" aria-labelledby="email-label" />
|
||||
```
|
||||
|
||||
### Fix: Insufficient Color Contrast
|
||||
|
||||
```css
|
||||
/* Before: 2.5:1 contrast */
|
||||
.text { color: #767676; }
|
||||
.text {
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
/* After: 4.5:1 contrast */
|
||||
.text { color: #595959; }
|
||||
.text {
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
/* Or add background */
|
||||
.text {
|
||||
@@ -456,25 +501,25 @@ lighthouse https://example.com --only-categories=accessibility
|
||||
// Make custom element keyboard accessible
|
||||
class AccessibleDropdown extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.setAttribute('tabindex', '0');
|
||||
this.setAttribute('role', 'combobox');
|
||||
this.setAttribute('aria-expanded', 'false');
|
||||
this.setAttribute("tabindex", "0");
|
||||
this.setAttribute("role", "combobox");
|
||||
this.setAttribute("aria-expanded", "false");
|
||||
|
||||
this.addEventListener('keydown', (e) => {
|
||||
this.addEventListener("keydown", (e) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
case "Enter":
|
||||
case " ":
|
||||
this.toggle();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'Escape':
|
||||
case "Escape":
|
||||
this.close();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
case "ArrowDown":
|
||||
this.focusNext();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
case "ArrowUp":
|
||||
this.focusPrevious();
|
||||
e.preventDefault();
|
||||
break;
|
||||
@@ -487,6 +532,7 @@ class AccessibleDropdown extends HTMLElement {
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- **Start early** - Accessibility from design phase
|
||||
- **Test with real users** - Disabled users provide best feedback
|
||||
- **Automate what you can** - 30-50% issues detectable
|
||||
@@ -494,6 +540,7 @@ class AccessibleDropdown extends HTMLElement {
|
||||
- **Document patterns** - Build accessible component library
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't rely only on automated testing** - Manual testing required
|
||||
- **Don't use ARIA as first solution** - Native HTML first
|
||||
- **Don't hide focus outlines** - Keyboard users need them
|
||||
|
||||
Reference in New Issue
Block a user