name: swiftui-accessibility-expert description: 'Expert guidance on SwiftUI accessibility: labels, traits, actions, focus management, Dynamic Type, motion, color, grouping, rotors, and testing. Use when building or reviewing SwiftUI views, fixing VoiceOver issues, adding accessibility support, reviewing accessibility compliance, or improving assistive technology experience.'
SwiftUI Accessibility
Overview
This skill provides comprehensive guidance for building SwiftUI apps accessible to everyone — including users of VoiceOver, Voice Control, Switch Control, and other assistive technologies across iOS, macOS, and visionOS.
Scope: SwiftUI accessibility modifiers, accessibility tree management, assistive technology support, Dynamic Type, motion/color/contrast preferences, accessibility testing.
Relationship to swiftui-expert-skill: The swiftui-expert-skill covers general SwiftUI best practices. This skill owns all accessibility concerns in depth — labels, traits, actions, grouping, focus management, motion, color, testing. Where topics overlap (e.g., Dynamic Type), this skill provides the authoritative accessibility perspective.
Agent Behavior Contract
- Every interactive element must have a meaningful
accessibilityLabel— "Delete" not "Button", "Close" not "X icon". Labels describe purpose, not appearance. Labels must not include the element type (VoiceOver adds it). - Decorative images must use
Image(decorative:)or.accessibilityHidden(true)— prevent VoiceOver from reading filenames or placeholder text for purely visual elements. - Use semantic controls —
Button,Toggle,Picker,Sliderinstead ofonTapGestureonText/Image. Semantic controls provide correct traits, actions, and values automatically. - Never use color alone to convey meaning — pair with text, icons, or shapes. Check
accessibilityDifferentiateWithoutColorfor additional non-color indicators. - Respect Dynamic Type — no hardcoded font sizes; use text styles (
.body,.headline) and@ScaledMetricfor custom dimensions. Test at accessibility sizes. - Animations must check
accessibilityReduceMotion— provide a static or fade alternative. Reduce means reduce, not remove — use subtle transitions instead of no animation. - Group related elements with
.accessibilityElement(children: .combine)— reduce VoiceOver stops. A card with title, subtitle, and image should be one stop, not three. - Test with Accessibility Inspector and VoiceOver — use
performAccessibilityAudit()in UI tests. Test at multiple Dynamic Type sizes, in dark mode, and with reduce motion enabled.
Quick Decision Tree
When a developer needs accessibility guidance:
- Adding labels or descriptions?
└─
references/accessible-descriptions.md— labels, values, hints, headings, custom content, speech - Making a control work with VoiceOver?
└─
references/accessible-controls.md— traits, actions, adjustable, gestures, representation - Handling Dynamic Type, color, or motion?
└─
references/accessible-appearance.md— text styles, @ScaledMetric, contrast, reduce motion - Fixing navigation or focus issues?
└─
references/accessible-navigation.md— sort priority, rotors, focus state, notifications - Reducing VoiceOver stops or structuring elements?
└─
references/accessible-grouping.md— combine, contain, ignore, conditional grouping, modal - Testing or auditing accessibility?
└─
references/accessible-testing.md— Inspector, VoiceOver checklist, performAccessibilityAudit
Triage-First Playbook
Common VoiceOver/accessibility errors and the next best move:
- VoiceOver reads "Button" with no label →
references/accessible-descriptions.md— addaccessibilityLabeldescribing the action - Decorative image announced by VoiceOver →
references/accessible-descriptions.md— useImage(decorative:)or.accessibilityHidden(true) - Too many VoiceOver stops on a card/row →
references/accessible-grouping.md— use.accessibilityElement(children: .combine) - Color-only indicator not conveyed →
references/accessible-appearance.md— pair with icon/text, checkaccessibilityDifferentiateWithoutColor - Custom control not interactive for VoiceOver →
references/accessible-controls.md— add correct traits and actions - VoiceOver reads elements in wrong order →
references/accessible-navigation.md— useaccessibilitySortPriority - Button says item name but no action context →
references/accessible-descriptions.md— addaccessibilityHintdescribing the navigation destination or result - Animations don't respect reduce motion →
references/accessible-appearance.md— checkaccessibilityReduceMotion, provide fade alternative - Focus not moving after content change →
references/accessible-navigation.md— use@AccessibilityFocusStateor postAccessibilityNotification
Core Patterns Reference
Labeling an Icon Button
Button(action: toggleFavorite) {
Image(systemName: isFavorite ? "heart.fill" : "heart")
}
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
Grouping a Card
HStack {
AsyncImage(url: movie.posterURL)
VStack(alignment: .leading) {
Text(movie.title)
Text(movie.year)
}
}
.accessibilityElement(children: .combine)
Conditional Grouping
HStack {
if isEditing {
Button("Delete") { delete() }
}
Text(movie.title)
}
.accessibilityElement(children: isEditing ? .contain : .combine)
Carousel with Per-Item Buttons and Hints
Use when each carousel item navigates to its own destination. Each item is an independent VoiceOver stop.
Carousel {
ForEach(Array(movies.enumerated()), id: \.offset) { offset, movie in
Button {
didSelectMovie(movie.id)
} label: {
MovieCard(movie: movie)
}
.accessibilityIdentifier("carousel.movie.\(offset)")
.accessibilityLabel(movie.title)
.accessibilityHint("View movie details")
.buttonStyle(.plain)
}
}
.accessibilityIdentifier("movies.carousel")
Warning: Do NOT use
.accessibilityElement()on the carousel container when items should be individually tappable — it collapses all children into one opaque element.
Checking Reduce Motion
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
ContentView()
.transition(reduceMotion ? .opacity : .move(edge: .trailing))
}
Dynamic Type with @ScaledMetric
@ScaledMetric(relativeTo: .body) private var posterHeight: CGFloat = 120
AsyncImage(url: posterURL)
.frame(height: posterHeight)
Review Checklist
Labels & Descriptions
- Every interactive element has a meaningful
accessibilityLabel - Navigation buttons with item-name labels have an
accessibilityHintdescribing the destination - Decorative images use
Image(decorative:)or.accessibilityHidden(true) - Labels describe purpose, not appearance
- Labels and hints are localized
Traits & Roles
- Semantic controls used (
Button,Toggle, notonTapGestureonText) - Headers marked with
.isHeaderor.h1–.h6 - Selected states use
.isSelectedtrait - Modal overlays use
.isModaltrait
Controls & Actions
- Custom actions provided for multi-action elements
- Adjustable action for carousels/steppers
- Escape action for custom dismissals
- Magic tap for media play/pause
Grouping & Hierarchy
- Related content grouped with
.combineto reduce VoiceOver stops - Interactive elements within groups still reachable (
.containwhen editing) - Redundant traits cleaned up after combining
- Canvas views provide
accessibilityChildren
Appearance & Adaptability
- No hardcoded font sizes — text styles or
@ScaledMetric - Color is not the sole indicator of meaning
- Animations respect
accessibilityReduceMotion - Images exempt from Smart Invert with
accessibilityIgnoresInvertColors() - Minimum tap targets: 44x44pt (iOS), 60x60pt (visionOS)
Testing
- Tested with VoiceOver enabled
- Tested at accessibility Dynamic Type sizes
-
performAccessibilityAudit()in UI tests - Tested in both light and dark mode for contrast
Reference Files
| File | Description |
|---|---|
references/_index.md |
Navigation index with quick links by problem |
references/accessible-descriptions.md |
Labels, values, hints, headings, custom content, speech customization |
references/accessible-controls.md |
Traits (all 17), actions, adjustable, gestures, representation |
references/accessible-appearance.md |
Dynamic Type, color/contrast, motion, transparency, AT checks |
references/accessible-navigation.md |
Sort priority, rotors, linked groups, focus, notifications, charts |
references/accessible-grouping.md |
Children behavior, conditional grouping, hidden, modal, Canvas |
references/accessible-testing.md |
Inspector, VoiceOver checklist, audits, environment overrides, visionOS |