| name | axiom-accessibility-diag |
| description | Use when fixing VoiceOver issues, Dynamic Type violations, color contrast failures, touch target problems, keyboard navigation gaps, or Reduce Motion support - comprehensive accessibility diagnostics with WCAG compliance, Accessibility Inspector workflows, and App Store Review preparation for iOS/macOS |
| license | MIT |
| metadata | {"version":"1.0.0"} |
Accessibility Diagnostics
Overview
Systematic accessibility diagnosis and remediation for iOS/macOS apps. Covers the 7 most common accessibility issues that cause App Store rejections and user complaints.
Core principle Accessibility is not optional. iOS apps must support VoiceOver, Dynamic Type, and sufficient color contrast to pass App Store Review. Users with disabilities depend on these features.
When to Use This Skill
- Fixing VoiceOver navigation issues (missing labels, wrong element order)
- Supporting Dynamic Type (text scaling for vision disabilities)
- Meeting color contrast requirements (WCAG AA/AAA)
- Fixing touch target size violations (< 44x44pt)
- Adding keyboard navigation (iPadOS/macOS)
- Supporting Reduce Motion (vestibular disorders)
- Preparing for App Store Review accessibility requirements
- Responding to user complaints about accessibility
The 7 Critical Accessibility Issues
1. VoiceOver Labels & Hints (CRITICAL - App Store Rejection)
Problem Missing or generic accessibility labels prevent VoiceOver users from understanding UI purpose.
WCAG 4.1.2 Name, Role, Value (Level A)
Common violations
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Button")
.accessibilityLabel("cart.badge.plus")
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
When to use hints
- Action is not obvious from label ("Add to cart" is obvious, no hint needed)
- Multi-step interaction ("Swipe right to confirm, left to cancel")
- State change ("Double-tap to toggle notifications on or off")
Decorative elements
Image("decorative-pattern")
.accessibilityHidden(true)
HStack {
Image(systemName: "star.fill")
Text("4.5")
Text("(234 reviews)")
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Rating: 4.5 stars from 234 reviews")
Testing
- Enable VoiceOver: Cmd+F5 (simulator) or triple-click side button (device)
- Navigate: Swipe right/left to move between elements
- Listen: Does VoiceOver announce purpose clearly?
- Check order: Does navigation order match visual layout?
2. Dynamic Type Support (HIGH - User Experience)
Problem Fixed font sizes prevent users with vision disabilities from reading text.
WCAG 1.4.4 Resize Text (Level AA - support 200% scaling without loss of content/functionality)
Common violations
Text("Price: $19.99")
.font(.system(size: 17))
UILabel().font = UIFont.systemFont(ofSize: 17)
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
Text("Price: $19.99")
.font(.body)
Text("Headline")
.font(.headline)
label.font = UIFont.preferredFont(forTextStyle: .body)
let customFont = UIFont(name: "CustomFont", size: 24)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
Custom sizes that scale with Dynamic Type
Text("Price: $19.99")
.font(.system(size: 17))
Text("Headline")
.font(Font.custom("CustomFont", size: 24))
Text("Large Title")
.font(.system(size: 60).relativeTo(.largeTitle))
Text("Custom Headline")
.font(.system(size: 24).relativeTo(.title2))
Text("Headline")
.font(.headline)
How relativeTo: works
- Base size: Your exact pixel size (24pt, 60pt, etc.)
- Scales with: The text style you specify (
.title2, .largeTitle, etc.)
- Result: When user increases text size in Settings, your custom size grows proportionally
Example
.title2 base: ~22pt → Your custom: 24pt (1.09x larger)
- User increases to "Extra Large" text
.title2 grows to ~28pt → Your custom grows to ~30.5pt (maintains 1.09x ratio)
Fix hierarchy (best to worst)
- Best: Use semantic styles (
.title, .body, .caption)
- Good: Use
.system(size:).relativeTo() for required custom sizes
- Acceptable: Custom font with
.dynamicTypeSize() modifier
- Unacceptable: Fixed sizes that never scale
SwiftUI text styles
.largeTitle - 34pt (scales to 44pt at accessibility sizes)
.title - 28pt
.title2 - 22pt
.title3 - 20pt
.headline - 17pt semibold
.body - 17pt (default)
.callout - 16pt
.subheadline - 15pt
.footnote - 13pt
.caption - 12pt
.caption2 - 11pt
Layout considerations
Text("Long product description...")
.font(.body)
.frame(height: 50)
Text("Long product description...")
.font(.body)
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
HStack {
Text("Label:")
Text("Value")
}
.dynamicTypeSize(...DynamicTypeSize.xxxLarge)
Testing
-
Xcode Preview: Environment override
.environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
-
Simulator: Settings → Accessibility → Display & Text Size → Larger Text → Drag to maximum
-
Device: Settings → Accessibility → Display & Text Size → Larger Text
-
Check: Does text remain readable? Does layout adapt? Is any text clipped?
3. Color Contrast (HIGH - Vision Disabilities)
Problem Low contrast text is unreadable for users with vision disabilities or in bright sunlight.
WCAG
- 1.4.3 Contrast (Minimum) — Level AA
- Normal text (< 18pt): 4.5:1 contrast ratio
- Large text (≥ 18pt or ≥ 14pt bold): 3:1 contrast ratio
- 1.4.6 Contrast (Enhanced) — Level AAA
- Normal text: 7:1 contrast ratio
- Large text: 4.5:1 contrast ratio
Common violations
Text("Warning")
.foregroundColor(.yellow)
Text("Info")
.foregroundColor(.gray)
Text("Warning")
.foregroundColor(.orange)
Text("Info")
.foregroundColor(.primary)
Text("Secondary")
.foregroundColor(.secondary)
Differentiate Without Color
Circle()
.fill(isAvailable ? .green : .red)
HStack {
Image(systemName: isAvailable ? "checkmark.circle.fill" : "xmark.circle.fill")
Text(isAvailable ? "Available" : "Unavailable")
}
.foregroundColor(isAvailable ? .green : .red)
if UIAccessibility.shouldDifferentiateWithoutColor {
}
Testing
- Use Color Contrast Analyzer tool (free download)
- Screenshot your UI, measure text vs background
- Check both light and dark mode
- Settings → Accessibility → Display & Text Size → Increase Contrast (test with this ON)
Quick reference
- Black (#000000) on White (#FFFFFF): 21:1 ✅ AAA
- Dark Gray (#595959) on White: 7:1 ✅ AAA
- Medium Gray (#767676) on White: 4.5:1 ✅ AA
- Light Gray (#959595) on White: 2.8:1 ❌ Fails
4. Touch Target Sizes (MEDIUM - Motor Disabilities)
Problem Small tap targets are difficult or impossible for users with motor disabilities.
WCAG 2.5.5 Target Size (Level AAA - 44x44pt minimum)
Apple HIG 44x44pt minimum for all tappable elements
Common violations
Button("×") {
dismiss()
}
.frame(width: 24, height: 24)
Image(systemName: "heart")
.font(.system(size: 16))
.onTapGesture { }
Button("×") {
dismiss()
}
.frame(minWidth: 44, minHeight: 44)
Image(systemName: "heart")
.font(.system(size: 24))
.frame(minWidth: 44, minHeight: 44)
.contentShape(Rectangle())
.onTapGesture { }
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
Spacing between targets
HStack(spacing: 4) {
Button("Edit") { }
Button("Delete") { }
}
HStack(spacing: 12) {
Button("Edit") { }
Button("Delete") { }
}
Testing
- Accessibility Inspector: Xcode → Open Developer Tool → Accessibility Inspector
- Select "Audit" tab → Run audit → Check for "Small Text" and "Hit Region" warnings
- Manual: Tap with one finger (not stylus) — can you hit it reliably without mistakes?
5. Keyboard Navigation (MEDIUM - iPadOS/macOS)
Problem Users who cannot use touch/mouse cannot navigate app.
WCAG 2.1.1 Keyboard (Level A - all functionality available via keyboard)
Common violations
.onTapGesture {
showDetails()
}
Button("Show Details") {
showDetails()
}
.keyboardShortcut("d", modifiers: .command)
struct CustomButton: View {
@FocusState private var isFocused: Bool
var body: some View {
Text("Custom")
.focusable()
.focused($isFocused)
.onKeyPress(.return) {
action()
return .handled
}
}
}
Focus management
.focusSection()
.defaultFocus($focus, .constant(true))
@FocusState private var focusedField: Field?
Button("Next") {
focusedField = .next
}
Testing (iPadOS/macOS)
- Connect keyboard to iPad or use Mac
- Press Tab - does focus move to interactive elements?
- Press Space/Return - does focused element activate?
- Check custom controls have visible focus indicator
- Can you reach all functionality without mouse/touch?
6. Reduce Motion Support (MEDIUM - Vestibular Disorders)
Problem Animations cause discomfort, nausea, or seizures for users with vestibular disorders.
WCAG 2.3.3 Animation from Interactions (Level AAA - motion animation can be disabled)
Common violations
.onAppear {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
ScrollView {
GeometryReader { geo in
Image("hero")
.offset(y: geo.frame(in: .global).minY * 0.5)
}
}
.onAppear {
if UIAccessibility.isReduceMotionEnabled {
scale = 1.0
} else {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
scale = 1.0
}
}
}
if UIAccessibility.isReduceMotionEnabled {
withAnimation(.linear(duration: 0.2)) {
showView = true
}
} else {
withAnimation(.spring()) {
showView = true
}
}
SwiftUI modifier
.animation(.spring(), value: isExpanded)
.transaction { transaction in
if UIAccessibility.isReduceMotionEnabled {
transaction.animation = nil
}
}
Testing
- Settings → Accessibility → Motion → Reduce Motion (toggle ON)
- Navigate app - are animations reduced or eliminated?
- Test: Transitions, scrolling effects, parallax, particle effects
- Video autoplay should also respect this preference
7. Common Violations (HIGH - App Store Review)
Images Without Labels
Image("product-photo")
Image("product-photo")
.accessibilityLabel("Red sneakers with white laces")
Image("background-pattern")
.accessibilityHidden(true)
Buttons With Wrong Traits
Text("Submit")
.onTapGesture {
submit()
}
Button("Submit") {
submit()
}
Text("Submit")
.accessibilityAddTraits(.isButton)
.onTapGesture {
submit()
}
Inaccessible Custom Controls
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
GeometryReader { geo in
}
.gesture(DragGesture()...)
}
}
struct CustomSlider: View {
@Binding var value: Double
var body: some View {
GeometryReader { geo in
}
.gesture(DragGesture()...)
.accessibilityElement()
.accessibilityLabel("Volume")
.accessibilityValue("\(Int(value))%")
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
value = min(value + 10, 100)
case .decrement:
value = max(value - 10, 0)
@unknown default:
break
}
}
}
}
Missing State Announcements
Button("Toggle") {
isOn.toggle()
}
Button("Toggle") {
isOn.toggle()
UIAccessibility.post(
notification: .announcement,
argument: isOn ? "Enabled" : "Disabled"
)
}
Button("Toggle") {
isOn.toggle()
}
.accessibilityValue(isOn ? "Enabled" : "Disabled")
Accessibility Inspector Workflow
1. Launch Accessibility Inspector
Xcode → Open Developer Tool → Accessibility Inspector
2. Select Target
- Dropdown: Choose running simulator or connected device
- Target: Select your app
3. Inspection Mode
- Click "Inspection Pointer" button (crosshair icon)
- Hover over UI elements to see:
- Label, Value, Hint, Traits
- Frame, Path
- Actions available
- Parent/child hierarchy
4. Run Audit
- Click "Audit" tab
- Click "Run Audit" button
- Review findings:
- Contrast — Color contrast issues
- Hit Region — Touch target size issues
- Clipped Text — Text truncation with Dynamic Type
- Element Description — Missing labels/hints
- Traits — Wrong accessibility traits
5. Fix and Re-Test
- Click each finding for details
- Fix in code
- Re-run audit to verify
VoiceOver Testing Checklist
Enable VoiceOver
- Simulator Cmd+F5 or Settings → Accessibility → VoiceOver
- Device Triple-click side button (if enabled in Settings)
Navigation Testing
- ☐ Swipe right/left - moves logically through UI elements
- ☐ Each element announces purpose clearly
- ☐ No unlabeled elements (except decorative)
- ☐ Heading navigation works (swipe up/down with 2 fingers)
- ☐ Container navigation works (swipe left/right with 3 fingers)
Interaction Testing
- ☐ Double-tap activates buttons
- ☐ Swipe up/down adjusts sliders/pickers (with
.accessibilityAdjustableAction)
- ☐ Custom gestures have VoiceOver equivalents
- ☐ Text fields announce keyboard type
- ☐ State changes are announced
Content Testing
- ☐ Images have descriptive labels or are hidden
- ☐ Error messages are announced
- ☐ Loading states are announced
- ☐ Modal sheets announce role
- ☐ Alerts announce automatically
App Store Review Preparation
Required Accessibility Features (iOS)
-
VoiceOver Support
- All UI elements must have labels
- Navigation must be logical
- All actions must be performable
-
Dynamic Type
- Text must scale from -3 to +12 sizes
- Layout must adapt without clipping
-
Sufficient Contrast
- Minimum 4.5:1 for normal text
- Minimum 3:1 for large text (≥18pt)
App Store Connect Metadata
When submitting:
-
Accessibility → Select features your app supports:
- ☑ VoiceOver
- ☑ Dynamic Type
- ☑ Increased Contrast
- ☑ Reduce Motion (if supported)
-
Test Notes: Document accessibility testing
Accessibility Testing Completed:
- VoiceOver: All screens tested with VoiceOver enabled
- Dynamic Type: Tested at all size categories
- Color Contrast: Verified 4.5:1 minimum contrast
- Touch Targets: All buttons minimum 44x44pt
- Reduce Motion: Animations respect user preference
Common Rejection Reasons
-
"App is not fully functional with VoiceOver"
- Missing labels on images/buttons
- Unlabeled custom controls
- Actions not performable with VoiceOver
-
"Text is not readable at all Dynamic Type sizes"
- Fixed font sizes
- Text clipping at large sizes
- Layout breaks at accessibility sizes
-
"Insufficient color contrast"
- Text fails 4.5:1 ratio
- UI elements fail 3:1 ratio
- Color-only indicators
Design Review Pressure: Defending Accessibility Requirements
The Problem
Under design review pressure, you'll face requests to:
- "Those VoiceOver labels make the code messy - can we skip them?"
- "Dynamic Type breaks our carefully designed layout - let's lock font sizes"
- "The high contrast requirement ruins our brand aesthetic"
- "44pt touch targets are too big - make them smaller for a cleaner look"
These sound like reasonable design preferences. But they violate App Store requirements and exclude 15% of users. Your job: defend using App Store guidelines and legal requirements, not opinion.
Red Flags — Designer Requests That Violate Accessibility
If you hear ANY of these, STOP and reference this skill:
- ❌ "Skip VoiceOver labels on icon-only buttons" – App Store rejection (Guideline 2.5.1)
- ❌ "Use fixed 14pt font for compact design" – Excludes users with vision disabilities
- ❌ "3:1 contrast ratio is fine" – Fails WCAG AA for text (needs 4.5:1)
- ❌ "Make buttons 36x36pt for clean aesthetic" – Fails touch target requirement (44x44pt minimum)
- ❌ "Disable Dynamic Type in this screen" – App Store rejection risk
- ❌ "Color-code without labels (red=error, green=success)" – Excludes colorblind users (8% of men)
How to Push Back Professionally
Step 1: Show the Guideline
"I want to support this design direction, but let me show you Apple's App Store
Review Guideline 2.5.1:
'Apps should support accessibility features such as VoiceOver and Dynamic Type.
Failure to include sufficient accessibility features may result in rejection.'
Here's what we need for approval:
1. VoiceOver labels on all interactive elements
2. Dynamic Type support (can't lock font sizes)
3. 4.5:1 contrast ratio for text, 3:1 for UI
4. 44x44pt minimum touch targets
Let me show where our design currently falls short..."
Step 2: Demonstrate the Risk
Open the app with accessibility features enabled:
- VoiceOver (Cmd+F5): Show buttons announcing "Button" instead of purpose
- Largest Text Size: Show layout breaking or text clipping
- Color Contrast Analyzer: Show failing contrast ratios
- Touch target overlay: Show targets < 44pt
Reference
- App Store Review Guideline 2.5.1
- WCAG 2.1 Level AA (industry standard)
- ADA compliance requirements (legal risk in US)
Step 3: Offer Compromise
"I can achieve your aesthetic goals while meeting accessibility requirements:
1. VoiceOver labels: Add them programmatically (invisible in UI, required for approval)
2. Dynamic Type: Use layout techniques that adapt (examples from Apple HIG)
3. Contrast: Adjust colors slightly to meet 4.5:1 (I'll show options that preserve brand)
4. Touch targets: Expand hit areas programmatically (visual size stays the same)
These changes won't affect the visual design you're seeing, but they're required
for App Store approval and legal compliance."
Step 4: Document the Decision
If overruled (designer insists on violations):
Slack message to PM + designer:
"Design review decided to proceed with:
- Fixed font sizes (disabling Dynamic Type)
- 38x38pt buttons (below 44pt requirement)
- 3.8:1 text contrast (below 4.5:1 requirement)
Important: These changes violate App Store Review Guideline 2.5.1 and WCAG AA.
This creates three risks:
1. App Store rejection during review (adds 1-2 week delay)
2. ADA compliance issues if user files complaint (legal risk)
3. 15% of potential users unable to use app effectively
I'm flagging this proactively so we can prepare a response plan if rejected."
Why this works
- You're not questioning their design taste
- You're raising App Store rejection risk (business impact)
- You're citing specific guidelines (not opinion)
- You're offering solutions that preserve visual design
- You're documenting the decision (protects you post-rejection)
Real-World Example: App Store Rejection (48-Hour Resubmit Window)
Scenario
- 48 hours until resubmit deadline after rejection
- Apple cited: "2.5.1 - Insufficient VoiceOver support"
- Designer says: "Just add generic labels quickly"
- PM watching the meeting, wants fastest fix
What to do
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Button")
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
.accessibilityHint("Double-tap to add this item to your shopping cart")
In the meeting, demonstrate
- Enable VoiceOver (Cmd+F5)
- Show "Button" announcement (generic - fails)
- Show "Add to cart" announcement (descriptive - passes)
- Reference Apple's rejection message: "Elements must have descriptive labels"
Time estimate 2-4 hours to audit all interactive elements and add proper labels.
Result
- Honest time estimate prevents second rejection
- Proper labels pass Apple review
- Resubmit accepted within 48 hours
When to Accept the Design Decision (Even If You Disagree)
Sometimes designers have valid reasons to override accessibility guidelines. Accept if:
Document in Slack
"Design review decided to proceed with [specific violations].
We understand this creates:
- App Store rejection risk (Guideline 2.5.1)
- Potential 1-2 week delay if rejected
- Need to audit and fix all instances if rejected
Monitoring plan:
- Submit for review with current design
- If rejected, implement proper accessibility (estimated 2-4 hours)
- Have accessibility-compliant version ready as backup"
This protects both of you and shows you're not blocking - just de-risking.
WCAG Compliance Levels
Level A (Minimum — Required for App Store)
- 1.1.1 Non-text Content — Images have text alternatives
- 2.1.1 Keyboard — All functionality via keyboard (iPadOS/macOS)
- 4.1.2 Name, Role, Value — Elements have accessible names
Level AA (Standard — Recommended)
- 1.4.3 Contrast (Minimum) — 4.5:1 text, 3:1 UI
- 1.4.4 Resize Text — Support 200% text scaling
- 1.4.5 Images of Text — Use real text when possible
Level AAA (Enhanced — Best Practice)
- 1.4.6 Contrast (Enhanced) — 7:1 text, 4.5:1 UI
- 2.3.3 Animation from Interactions — Reduce Motion support
- 2.5.5 Target Size - 44x44pt minimum targets
Goal Meet Level AA for all content, Level AAA where feasible.
Quick Command Reference
After making fixes:
/axiom:audit-accessibility
$axiom:accessibility-diag
Resources
Docs: /accessibility/voiceover, /uikit/uifont/scaling_fonts_automatically
Remember Accessibility is not a feature, it's a requirement. 15% of users have some form of disability. Making your app accessible isn't just the right thing to do - it expands your user base and improves the experience for everyone.