How do you make an iOS app accessible?
TL;DR
Accessibility means everyone can use your app, including people who are blind, have low vision, or have motor or hearing problems. The basics:
- VoiceOver: correct labels, values, traits and hints.
- Dynamic Type: text grows with the user's setting, and layout still works.
- Contrast and color: do not rely on color alone.
- Tap targets: at least 44 x 44 points.
- Motion: respect Reduce Motion.
- Test with VoiceOver and Accessibility Inspector.
VoiceOver: label, value, trait, hint
| Property | Meaning | Example |
|---|---|---|
| Label | What it is | "Add to cart" |
| Value | Its current state | "3 items", "50 percent" |
| Traits | What kind of thing | Button, header, selected |
| Hint | What happens (optional) | "Adds the item to your cart" |
SwiftUI:
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")
Text("Settings")
.font(.title)
.accessibilityAddTraits(.isHeader)UIKit:
cartButton.accessibilityLabel = "Add to cart"
titleLabel.accessibilityTraits.insert(.header)
volumeSlider.accessibilityValue = "\(Int(volume * 100)) percent"Labels should be short and should not include the type. Say "Delete", not "Delete button". VoiceOver adds "button" from the trait.
Group related content
A card with an image, title, price and rating should often be one element.
VStack(alignment: .leading) {
Text(product.name)
Text(product.price, format: .currency(code: "USD"))
RatingView(rating: product.rating)
}
.accessibilityElement(children: .combine)Hide pure decoration:
Image("divider").accessibilityHidden(true)
Image(decorative: "background")Custom actions
Swipe actions and small buttons inside a cell are hard to reach. Add them as actions:
MessageRow(message: message)
.accessibilityAction(named: "Delete") { delete(message) }
.accessibilityAction(named: "Reply") { reply(to: message) }Dynamic Type
// SwiftUI: built-in text styles scale automatically
Text("Order summary").font(.headline)
// Scale your own sizes too
@ScaledMetric(relativeTo: .body) private var iconSize = 24.0
// UIKit
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0Test with the largest accessibility sizes. Layouts should wrap or switch from horizontal to vertical, not cut text.
@Environment(\.dynamicTypeSize) private var typeSize
var body: some View {
let layout = typeSize.isAccessibilitySize
? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout { avatar; details }
}Color, contrast and motion
- Aim for a contrast ratio of at least 4.5:1 for normal text.
- Never show state with color only. Add an icon or text ("Error: ...").
- Support Dark Mode and Increase Contrast.
- Check
accessibilityReduceMotionand replace big animations with fades:
@Environment(\.accessibilityReduceMotion) private var reduceMotion
.animation(reduceMotion ? nil : .spring, value: isExpanded)Announcements and focus
// UIKit: tell VoiceOver something changed
UIAccessibility.post(notification: .announcement, argument: "Upload finished")
// SwiftUI: move VoiceOver focus
@AccessibilityFocusState private var isErrorFocused: Bool
Text(errorMessage).accessibilityFocused($isErrorFocused)How to test
- Turn on VoiceOver and use the screen with your eyes closed.
- Accessibility Inspector (Xcode > Open Developer Tool) shows labels and runs an audit.
- UI tests can run an audit:
try app.performAccessibilityAudit()(Xcode 15+). - Try the largest text size, Bold Text, Reduce Motion and Voice Control.
Why it matters
It is the right thing to do, it is required by law in many places, and it helps every user. The App Store now also shows Accessibility Nutrition Labels (announced in 2025). Developers declare which features their app supports, like VoiceOver or larger text.
Common follow-up questions
accessibilityLabelvsaccessibilityIdentifier? The label is read aloud to users. The identifier is only for UI tests and is never spoken.- What does VoiceOver read for an image with no label? Possibly the file name, which is confusing. Always label or hide images.
- What is the minimum tap target? 44 x 44 points.
- How do you handle custom controls? Give them the right traits, a value, and support
accessibilityIncrement/accessibilityDecrementfor adjustable controls. - What is the rotor? A VoiceOver gesture to jump between headings, links or custom groups. Header traits make it useful.