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.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
WCAG compliance and accessibility quality assurance for iOS applications
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:
Use this skill when:
This skill covers:
| 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 | - |
The accessibility tree IS your primary testing interface.
Unlike visual testing (screenshots), the accessibility tree reveals:
3-4x faster and more reliable than visual inspection.
Level A (Minimum):
Level AA (Recommended):
Level AAA (Enhanced):
Target Level AA for most apps.
Essential Properties:
accessibilityLabel: What element is
accessibilityValue: Current state/value
accessibilityHint: What happens when activated
accessibilityTraits: Element behavior
isAccessibilityElement: Should be exposed
true for interactive/informative elementsfalse for decorative elementsAccessibility 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.
Start here to assess app's accessibility implementation:
{
"operation": "check-accessibility",
"target": "booted"
}
Interprets accessibility tree quality:
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".
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:
Find and verify accessibility of specific UI elements:
{
"operation": "find-element",
"target": "booted",
"parameters": {
"query": "Submit button"
}
}
Validates:
Verify logical focus order:
Focus order issues:
Bad: [Button: Cancel] → [Image: Decorative] → [Button: Submit] → [TextField]
Good: [TextField] → [Button: Submit] → [Button: Cancel]
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:
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
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
Minimum contrast requirements:
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:
Note: Screenshots are appropriate for contrast testing (color-based).
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": ""
}
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:
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"
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.
Detection:
Query accessibility tree
Find elements with type: "Image"
Check if label is missing or generic
Symptoms:
Fix:
imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Descriptive text"
// Or if decorative:
imageView.isAccessibilityElement = false
Detection:
Query accessibility tree
Find elements with traits: ["Button"]
Check if label is missing or just "button"
Symptoms:
Fix:
button.accessibilityLabel = "Send message"
// Avoid: button.accessibilityLabel = "Send message button" // Redundant
Detection:
Screenshot → Sample colors
Calculate contrast ratio
Compare to WCAG standards
Symptoms:
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
Detection:
Check if functionality requires:
- Multi-finger gestures
- Precise timing
- Specific swipe patterns
Symptoms:
Fix:
// Provide alternative single-tap interaction
// Or use standard UIControl components
// Avoid custom gesture-only interfaces
Detection:
Query accessibility tree
Find elements with traits: ["Link"]
Check if label is generic: "click here", "read more"
Symptoms:
Fix:
// Bad
link.accessibilityLabel = "Click here"
// Good
link.accessibilityLabel = "Read our privacy policy"
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"
}
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)
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
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"
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:
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
Symptoms:
describe doesn't show itSolutions:
isAccessibilityElement = trueisHidden = falseaccessibilityElementsHidden = false on parentsSymptoms:
Solutions:
accessibilityElements array to set explicit order:
containerView.accessibilityElements = [label, field, button]
Symptoms:
Solutions:
containerView.isAccessibilityElement = true
containerView.accessibilityLabel = "Email from John Doe, unread"
Design with accessibility from the start:
iOS equivalent:
Automated testing catches technical issues, but:
// 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
Set standards and enforce:
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")
This Skill works with these MCP tools:
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)
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 examplesRemember: Accessibility tree is ground truth. Build accessible from the start. Test with real users.