| name | accessibility-testing |
| description | WCAG compliance testing and accessibility quality assurance workflows for iOS apps. Use when validating accessibility labels, testing VoiceOver compatibility, checking contrast ratios, or ensuring WCAG 2.1 compliance. Covers accessibility tree analysis, semantic validation, and automated accessibility testing patterns. |
| version | 0.0.1 |
| token_cost | ~40 |
Accessibility Testing Skill
WCAG compliance and accessibility quality assurance for iOS applications
Overview
This skill teaches accessibility-first testing strategies for iOS apps. Accessibility testing ensures apps are usable by everyone, including people with disabilities. It combines automated validation of accessibility metadata with manual verification of user experience patterns.
Why accessibility testing matters:
- Legal compliance: WCAG 2.1 is required in many jurisdictions
- User reach: 15% of population has some form of disability
- Better UX: Accessible apps are better for everyone
- SEO benefits: Better structure improves discoverability
- Cost savings: Fix issues early vs. retrofitting
When to Use This Skill
Use this skill when:
- Validating accessibility compliance before release
- Debugging VoiceOver issues or user reports
- Implementing new features that need accessibility support
- Running accessibility audits as part of CI/CD
- Testing Dynamic Type support and contrast ratios
- Verifying semantic markup and element roles
- Checking keyboard navigation and focus management
This skill covers:
- Accessibility tree analysis and interpretation
- WCAG 2.1 compliance checking (A, AA, AAA levels)
- VoiceOver testing patterns
- Dynamic Type validation
- Common accessibility violations and fixes
Quick Reference
| Task | Tool/Operation | Typical Time |
|---|---|---|
| Check accessibility quality | accessibility-quality-check |
~80ms |
| Query full accessibility tree | idb-ui-describe |
~120ms |
| Find element by label | idb-ui-find-element |
~100ms |
| Screenshot (fallback only) | screenshot |
~2000ms |
| VoiceOver simulation | Manual testing | - |
Core Principle: Accessibility Tree First
The accessibility tree IS your primary testing interface.
Unlike visual testing (screenshots), the accessibility tree reveals:
- What screen readers "see"
- Semantic structure and relationships
- Focus order and navigation paths
- Element roles and states
- Text alternatives for non-text content
3-4x faster and more reliable than visual inspection.
Key Concepts
WCAG 2.1 Levels
Level A (Minimum):
- Text alternatives for images
- Keyboard accessibility
- No color-only information
- Headings and labels present
Level AA (Recommended):
- Contrast ratio 4.5:1 for normal text
- Contrast ratio 3:1 for large text
- Resize text up to 200%
- Meaningful focus order
- Descriptive labels and instructions
Level AAA (Enhanced):
- Contrast ratio 7:1 for normal text
- Contrast ratio 4.5:1 for large text
- No timing requirements
- Comprehensive error handling
Target Level AA for most apps.
iOS Accessibility Properties
Essential Properties:
accessibilityLabel: What element is
- Example: "Profile photo", "Send message button"
- Should be concise, descriptive
accessibilityValue: Current state/value
- Example: "50%", "Selected", "3 of 10"
accessibilityHint: What happens when activated
- Example: "Opens your profile settings"
- Use sparingly, only when action isn't obvious
accessibilityTraits: Element behavior
- Button, Link, Header, Selected, etc.
isAccessibilityElement: Should be exposed
truefor interactive/informative elementsfalsefor decorative elements
Accessibility Tree vs Visual UI
Accessibility tree is semantic, not visual:
Visual UI:
┌─────────────────┐
│ [img] John Doe │ ← Visual: Image + Text
│ Online │
└─────────────────┘
Accessibility Tree:
• Button: "John Doe, Online, Profile" ← Single focusable element
- label: "John Doe"
- value: "Online"
- hint: "Opens profile"
- traits: [Button]
Good accessibility = logical semantic structure.
Standard Workflow
1. Initial Accessibility Quality Check
Start here to assess app's accessibility implementation:
{
"operation": "check-accessibility",
"target": "booted"
}
Interprets accessibility tree quality:
- "excellent": 90%+ elements have labels, good semantic structure
- "good": 70-90% coverage, minor issues
- "fair": 50-70% coverage, needs improvement
- "poor": <50% coverage, serious accessibility problems
- "insufficient": Cannot perform accessibility-based testing
Decision tree:
excellent/good → Proceed with accessibility-first testing
fair → Test but expect to find issues
poor → Major remediation needed
insufficient → App may not support assistive tech
Note: Most modern iOS apps score "good" or "excellent".
2. Query Accessibility Tree
Core operation for all accessibility testing:
{
"operation": "describe",
"target": "booted",
"parameters": {
"operation": "all"
}
}
Returns complete accessibility tree:
{
"elements": [
{
"label": "Login",
"type": "Button",
"frame": { "x": 100, "y": 400, "width": 175, "height": 50 },
"enabled": true,
"visible": true,
"traits": ["Button"],
"value": null,
"hint": "Sign in to your account"
},
{
"label": "Email address",
"type": "TextField",
"value": "",
"frame": { "x": 50, "y": 300, "width": 275, "height": 44 },
"enabled": true,
"visible": true,
"traits": ["TextField"]
}
]
}
Analyze for:
- All interactive elements have labels
- Labels are descriptive and concise
- Proper element types/traits
- Logical reading order (top-to-bottom, left-to-right)
- No duplicate or confusing labels
3. Validate Specific Elements
Find and verify accessibility of specific UI elements:
{
"operation": "find-element",
"target": "booted",
"parameters": {
"query": "Submit button"
}
}
Validates:
- Element exists in accessibility tree
- Has appropriate label
- Correct type and traits
- Enabled/visible state correct
4. Test Navigation and Focus
Verify logical focus order:
- Query accessibility tree
- Check elements appear in logical order
- No gaps in navigation
- Focus lands on meaningful elements first
Focus order issues:
Bad: [Button: Cancel] → [Image: Decorative] → [Button: Submit] → [TextField]
Good: [TextField] → [Button: Submit] → [Button: Cancel]
WCAG Compliance Workflows
Testing Checklist
1. Text Alternatives (WCAG 1.1)
All non-text content needs text alternative:
Query accessibility tree for all images
For each image:
✓ Has accessibilityLabel
✓ Label describes image content
✓ Or marked as decorative (isAccessibilityElement: false)
Common violations:
- Images with no label
- Generic labels: "image", "icon", "photo"
- Labels don't describe content: "png_12345"
Fix:
// Bad
imageView.isAccessibilityElement = true // No label
// Good
imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Profile photo of John Doe"
// Decorative (best if truly decorative)
imageView.isAccessibilityElement = false
2. Keyboard/Focus Navigation (WCAG 2.1)
All functionality available via sequential navigation:
Query accessibility tree
Verify elements appear in logical order:
✓ Top to bottom
✓ Left to right
✓ Grouped logically
✓ Interactive elements are focusable
✓ Decorative elements are not focusable
Test pattern:
1. describe → Get all elements
2. Map order: element[0], element[1], element[2]...
3. Verify order matches visual/logical flow
4. Check no important elements missing
3. Contrast Ratios (WCAG 1.4.3)
Minimum contrast requirements:
- Normal text (<18pt): 4.5:1 ratio (AA), 7:1 ratio (AAA)
- Large text (≥18pt or ≥14pt bold): 3:1 ratio (AA), 4.5:1 (AAA)
- UI components: 3:1 ratio (AA)
Testing approach:
1. screenshot → Capture current screen
2. Use color picker to sample text/background
3. Calculate contrast ratio: (L1 + 0.05) / (L2 + 0.05)
4. Verify meets WCAG AA (4.5:1 or 3:1)
Common violations:
- Gray text on white: 2.5:1 (fails)
- Light blue on white: 2.8:1 (fails)
- Dark gray on white: 4.6:1 (passes AA)
Note: Screenshots are appropriate for contrast testing (color-based).
4. Labels and Instructions (WCAG 3.3.2)
All inputs have clear labels:
Query accessibility tree
For each TextField/input element:
✓ Has accessibilityLabel
✓ Label describes purpose
✓ Label visible or programmatically associated
✓ Required fields indicated
✓ Format instructions provided if needed
Good labels:
{
"type": "TextField",
"label": "Email address",
"hint": "Enter your email to sign in",
"value": ""
}
Bad labels:
{
"type": "TextField",
"label": "TextField", // Generic
"value": ""
}
5. Dynamic Type Support (iOS-specific)
Text scales from 100% to 200%:
Test pattern:
1. Set Dynamic Type to smallest size
2. Launch app, screenshot, verify readable
3. Set Dynamic Type to largest size
4. Launch app, screenshot, verify:
✓ Text scales appropriately
✓ No text truncation
✓ Layout adapts
✓ Buttons still tappable
Settings locations:
- Settings → Accessibility → Display & Text Size → Larger Text
- Simulator: Accessibility Inspector → Settings
VoiceOver Testing Patterns
Simulating VoiceOver Experience
VoiceOver announces elements in accessibility tree order:
1. describe → Get accessibility tree
2. For each element (in order):
- Announces: [label] [value] [traits] [hint]
- Example: "Submit button, button, Sign in to your account"
3. Verify announcements are:
✓ Clear and descriptive
✓ Not redundant
✓ Appropriate detail level
Announcement structure:
"[label], [type], [value], [hint]"
Examples:
"Profile photo, image, image of John Doe"
"Volume, slider, 50%, adjustable"
"Send, button, button, Sends your message"
Common VoiceOver Issues
1. Verbose Announcements
Bad: "Submit button, button, Click here to submit the form"
Good: "Submit, button"
Fix: Remove redundant "button" from label, concise hint.
2. Missing Context
Bad: "Edit, button" (which item?)
Good: "Edit profile photo, button"
Fix: Include context in label.
3. Confusing Order
Visual: [Title] [Close button]
[Content]
VoiceOver: Close button → Content → Title ❌
Fix: Adjust accessibility container order or element grouping.
4. No Label
Element visible, but:
- isAccessibilityElement: false (should be true)
- Or no accessibilityLabel
Fix: Set both properties appropriately.
Common Accessibility Violations
Issue: Image Without Label
Detection:
Query accessibility tree
Find elements with type: "Image"
Check if label is missing or generic
Symptoms:
- VoiceOver says "image" or nothing
- Screen reader users can't identify image
Fix:
imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Descriptive text"
// Or if decorative:
imageView.isAccessibilityElement = false
Issue: Buttons Without Labels
Detection:
Query accessibility tree
Find elements with traits: ["Button"]
Check if label is missing or just "button"
Symptoms:
- VoiceOver says "button" with no context
- Users don't know what button does
Fix:
button.accessibilityLabel = "Send message"
// Avoid: button.accessibilityLabel = "Send message button" // Redundant
Issue: Low Contrast Text
Detection:
Screenshot → Sample colors
Calculate contrast ratio
Compare to WCAG standards
Symptoms:
- Hard to read in bright light
- Fails WCAG AA (4.5:1)
Fix:
// Increase contrast
label.textColor = .label // System adapts to dark mode
// Or explicit colors with sufficient contrast
label.textColor = UIColor(white: 0.2, alpha: 1.0) // Dark gray on white
Issue: Complex Gestures Required
Detection:
Check if functionality requires:
- Multi-finger gestures
- Precise timing
- Specific swipe patterns
Symptoms:
- Motor-impaired users can't activate
- VoiceOver users can't navigate
Fix:
// Provide alternative single-tap interaction
// Or use standard UIControl components
// Avoid custom gesture-only interfaces
Issue: Non-Descriptive Link Text
Detection:
Query accessibility tree
Find elements with traits: ["Link"]
Check if label is generic: "click here", "read more"
Symptoms:
- Screen reader users don't know destination
- Can't scan links effectively
Fix:
// Bad
link.accessibilityLabel = "Click here"
// Good
link.accessibilityLabel = "Read our privacy policy"
Advanced Testing Patterns
Pattern: Form Validation
Test accessibility of error states:
1. describe → Get form elements
2. Submit invalid form
3. describe → Check error state
4. Verify:
✓ Error messages have labels
✓ Associated with relevant field
✓ Clear instructions for fixing
✓ Focus moves to first error
Good error accessibility:
{
"type": "TextField",
"label": "Email address",
"value": "invalid",
"traits": ["TextField"],
"hint": "Invalid email format. Example: user@example.com"
}
Pattern: Dynamic Content
Test when content updates:
1. describe → Get initial state
2. Trigger update (load more, filter, etc.)
3. describe → Get new state
4. Verify:
✓ New content has labels
✓ Loading states announced
✓ Focus managed appropriately
✓ No duplicate announcements
Use accessibility notifications:
// Announce completion
UIAccessibility.post(notification: .announcement,
argument: "10 new messages loaded")
// Or move focus to new content
UIAccessibility.post(notification: .layoutChanged,
argument: firstNewElement)
Pattern: Modal/Dialog Testing
Test focus management:
1. describe → Get main screen elements
2. Open modal
3. describe → Get modal elements
4. Verify:
✓ Focus trapped in modal
✓ Background content not accessible
✓ Close button clearly labeled
✓ Modal has accessible title
5. Close modal
6. describe → Verify focus returns appropriately
Pattern: Pagination/Lists
Test large scrollable content:
For each page/section:
1. describe → Get visible elements
2. Verify logical reading order
3. Check:
✓ Item count announced ("Item 1 of 10")
✓ Headings mark sections
✓ Load more/pagination clear
Good pagination labels:
cell.accessibilityLabel = "Message from John, Item 5 of 42"
loadMoreButton.accessibilityLabel = "Load 20 more messages"
Automated Testing Integration
CI/CD Accessibility Checks
Basic audit in test pipeline:
1. Launch app to key screen
2. accessibility-quality-check → Get score
3. Assert score >= "good"
4. describe → Capture accessibility tree
5. Validate:
- All buttons have labels
- No images without labels (excluding decorative)
- No duplicate labels in same context
- Logical element order
Fail build if:
- Score is "poor" or "insufficient"
- Critical elements missing labels
- New violations introduced (regression)
Regression Testing
Track accessibility over time:
1. Capture baseline accessibility tree (JSON)
2. On each commit:
- Capture current tree
- Compare to baseline
- Flag new missing labels
- Flag changed reading order
3. Review and approve changes or fix regressions
Troubleshooting
Problem: Element Not in Accessibility Tree
Symptoms:
- Element visible but
describedoesn't show it - VoiceOver skips element
Solutions:
- Check
isAccessibilityElement = true - Verify not hidden:
isHidden = false - Check not covered by another element
- Ensure frame is not CGRect.zero
- Verify
accessibilityElementsHidden = falseon parents
Problem: Wrong Reading Order
Symptoms:
- Elements announced in illogical order
- Navigation jumps around screen
Solutions:
- Check view hierarchy (Z-order affects reading order)
- Use
accessibilityElementsarray to set explicit order:containerView.accessibilityElements = [label, field, button] - Group related elements in accessibility containers
Problem: Insufficient Context
Symptoms:
- Labels too generic: "button", "image", "item"
- Users can't identify purpose
Solutions:
- Add specific context to label
- Include state information in value
- Use hint for non-obvious actions
- Consider combining multiple elements:
containerView.isAccessibilityElement = true containerView.accessibilityLabel = "Email from John Doe, unread"
Best Practices
1. Accessibility-First Development
Design with accessibility from the start:
- Use standard UIKit components (built-in support)
- Plan semantic structure before visual design
- Consider screen reader experience in wireframes
- Test with VoiceOver during development
2. Semantic HTML/Structure
iOS equivalent:
- Use appropriate UITraits (Button, Header, Link, etc.)
- Group related content in accessibility containers
- Use heading traits to mark sections
- Adjust traits to match element behavior
3. Test with Real Users
Automated testing catches technical issues, but:
- Real screen reader users find UX problems
- Motor-impaired users reveal interaction issues
- Test with VoiceOver on actual device
- Observe usage patterns, not just compliance
4. Document Accessibility Decisions
// Intentionally not accessible - purely decorative
backgroundImage.isAccessibilityElement = false
// Combined for better experience - announces as single element
cardView.isAccessibilityElement = true
cardView.accessibilityLabel = "\(title), \(date), \(author)"
titleLabel.isAccessibilityElement = false
dateLabel.isAccessibilityElement = false
authorLabel.isAccessibilityElement = false
5. Maintain Accessibility Tree Quality
Set standards and enforce:
- Minimum quality score: "good"
- All interactive elements have labels
- All images have labels or marked decorative
- Contrast ratios meet AA standards
- Forms have clear labels and error handling
Measure in CI/CD:
Run: accessibility-quality-check
Assert: score >= "good"
Run: describe → Parse accessibility tree
Assert: All buttons have labels
Assert: No generic labels ("button", "image")
Integration with MCP Tools
This Skill works with these MCP tools:
- accessibility-quality-check: Quick assessment (~80ms)
- idb-ui-describe: Full accessibility tree (~120ms)
- idb-ui-find-element: Semantic element search (~100ms)
- screenshot: Visual inspection (contrast, layout) (~2000ms)
Workflow integration:
1. accessibility-quality-check → Assess app quality
2. idb-ui-describe → Get detailed tree
3. Analyze semantic structure
4. screenshot → Verify contrast/visual (if needed)
Related Skills
- ui-automation-workflows: Accessibility-first UI automation
- ios-testing-patterns: Test strategy and CI integration
- simulator-workflows: Device management for testing
Related Resources
xc://reference/accessibility: Accessibility API referencexc://reference/wcag: WCAG 2.1 guidelines mapped to iOSxc://workflows/accessibility-first: This workflow patternxc://examples/voiceover-testing: VoiceOver test examples
Remember: Accessibility tree is ground truth. Build accessible from the start. Test with real users.