A Complete SwiftUI Accessibility Audit: VoiceOver, Dynamic Type, and Hit Targets
Accessibility is not a single Inspector run before release. SwiftUI derives useful semantics from native controls such as Button, TextField, and Toggle, but custom layouts, icon-only actions, and motion still require design decisions. A meaningful audit covers the whole task: semantics, reading order, text scaling, interaction targets, contrast, and motion preferences.
The standard is not “every element has a label.” The standard is that a user can understand the state, complete the task, and recover from an error.
Complete a task without looking at the screen
Enable VoiceOver and perform one core journey from its true entry point. For a reading app, that might mean finding a book, marking it as a favorite, opening its details, and navigating back. Every focused element should answer three questions: what is it, what is its current state, and what will activation do?
If VoiceOver announces “heart” or an asset filename, implementation details have leaked into the interface. Preserve native button semantics and give an icon action a label based on its behavior:
struct FavoriteButton: View {
@Binding var isFavorite: Bool
var body: some View {
Button {
isFavorite.toggle()
} label: {
Image(systemName: isFavorite ? "heart.fill" : "heart")
.frame(minWidth: 44, minHeight: 44)
.contentShape(Rectangle())
}
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
.accessibilityValue(isFavorite ? "Favorite" : "Not favorite")
}
}
The label describes the action, and the value communicates current state. Avoid adding a generic hint such as “double tap to activate”; VoiceOver already teaches the standard button gesture. A hint is useful only when the result is not apparent from the label.
Inspect reading order and grouping
A visually attractive HStack does not guarantee a coherent spoken sequence. A title, author, and status that jointly describe one book may be combined with .accessibilityElement(children: .combine). Keep a separate favorite button outside that group because it has its own action.
Prefer fixing source order and layout structure over applying several accessibilitySortPriority values. Priorities can repair a layout that cannot otherwise change, but they are easy to leave stale after a redesign.
Decorative images should use Image(decorative:) or be hidden from accessibility. Do not hide an image merely because text is nearby. If a chart, cover, or status icon communicates information absent from the text, it still needs an equivalent description.
Test the largest text, not one larger size
Move through every accessibility text size, including the largest. Fixed heights, single-line assumptions, and dense horizontal rows are common failure points. Allow labels to wrap. Use an adaptive vertical arrangement or ViewThatFits when a horizontal composition no longer has room. Avoid using minimumScaleFactor to shrink essential text back into an unreadable space.
Look beyond clipping. Is the primary action still encountered in a logical order? Does an error remain near its field? After scrolling, can focus find newly revealed content? Does a sheet’s heading remain identifiable?
Custom fonts should scale relative to a text style instead of using a fixed point size. Also verify that bold-text settings do not cause labels to collide with controls.
Separate visual size from the hit target
An icon can remain visually small while its interactive region is generous. The example uses a minimum frame and contentShape so transparent padding participates in hit testing. Leave space between adjacent actions; two nominally large targets can still be difficult if their boundaries touch.
Test interaction beyond direct touch. Connect a hardware keyboard and inspect focus order. Use Switch Control for a representative workflow. A gesture-only operation, such as swipe to delete, needs an accessible action or visible alternative.
Check color and motion preferences
Color cannot be the only carrier of meaning. Pair an error color with text or an icon, and expose selected state through the accessibility value. Check light and dark appearances, Increase Contrast, and Reduce Transparency. Text placed over a material may lose the separation that looked adequate in the default setting.
When Reduce Motion is enabled, replace large scale, parallax, or spatial transitions with a subtle fade or no animation. The goal is not to remove every state change; it is to preserve orientation without forcing motion that a user asked to reduce.
Turn the audit into a regression routine
Keep a short release checklist for important screens: the VoiceOver journey, largest text, portrait and landscape where supported, light and dark appearance, increased contrast, reduced transparency, reduced motion, and keyboard or switch focus. Automated audits can find missing labels and some contrast issues, but they cannot judge whether announcements sound natural or whether the workflow makes sense.
Start with semantically correct native controls, then add only the modifiers that communicate product-specific meaning. That approach produces a more stable accessibility tree than rebuilding control behavior from gestures and decorative views, and it gives future interface changes a safer foundation.