Home / Blog / iOS accessibility: the minimum VoiceOver and Dynamic Type discipline I won’t skip

iOS accessibility: the minimum VoiceOver and Dynamic Type discipline I won’t skip

Most iOS devs treat accessibility as an afterthought. Here's the minimum set of things I actually do on every project.

iOS has a very strong accessibility story. Apple invests heavily in it. VoiceOver (screen reader), Dynamic Type (font scaling), Reduce Motion, High Contrast, Voice Control, Switch Control. The OS adapts its behaviour to the user’s needs.

But most apps don’t handle any of it. The developer says “I’ll get to it later” and never does. Across my 12 iOS apps, a minimum accessibility discipline is a hard rule. Here’s the practical checklist.

VoiceOver basics

VoiceOver reads the elements on screen while the user navigates with gestures. The core rule: every interactive element has to be readable and make sense out loud.

SwiftUI gives you sensible defaults. Text gets read. A button’s label gets read. For custom UI you do it manually:

Image("icon-share")
    .accessibilityLabel("Share")
    .accessibilityHint("Tap to share this content")
  • accessibilityLabel: the text VoiceOver reads. A meaningful label, not the default image name (which is useless).
  • accessibilityHint: an extra description of what the action does. Something like “Opens the share menu”.

Decorative vs informative images

Don’t read every icon. Hide the decorative ones:

Image("background-decoration")
    .accessibilityHidden(true)

That way VoiceOver skips the background flourishes and only reads images that convey information.

Rule: if the image carries meaning (“success”, “warning”, “error”), give it a label. If it’s purely decorative, hide it.

Group related elements

Users navigate with VoiceOver item by item. Stepping through 10 elements one at a time is exhausting. Group related ones together:

HStack {
    Image("user-avatar")
    VStack {
        Text("Ahmet Yılmaz")
        Text("Software Engineer")
    }
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Ahmet Yılmaz, Software Engineer")

Now three elements (image plus two texts) are read as one. Navigation gets faster.

Dynamic Type (font scaling)

Users change their font size in Settings > Display > Text Size. There’s a spectrum from Extra Small (12pt base) up to Accessibility XXX Large (53pt base).

Old apps don’t handle this, they use fixed fonts. The user can’t read the text at all.

SwiftUI is Dynamic Type responsive by default:

Text("Title")
    .font(.title)           // Dynamic Type friendly
    .font(.largeTitle)      // Dynamic Type friendly
    .font(.system(size: 16)) // NOT Dynamic Type

System sizes (.title, .body, .caption) scale with user preference. Manual size (.system(size: 16)) does not.

Rule: use system sizes when you can. If you need a custom size, use the .dynamicTypeSize() modifier:

Text("custom")
    .font(.system(size: 16))
    .dynamicTypeSize(...DynamicTypeSize.accessibility5)

Layout has to adapt to dynamic font sizes

When the font grows the layout breaks. Classic problem: button text wraps to two lines, button height is fixed, text gets clipped.

Fix: dynamic button height:

Button("Save") { }
    .frame(maxWidth: .infinity)
    .padding(.vertical, 12)
    .background(Color.blue)
// Note: do NOT write .frame(height: 44)

Padding instead of fixed height. As the text grows, so does the button.

For multi-line overflow inside an HStack:

HStack(alignment: .top) {
    Image("icon")
    Text("A long label that might wrap to two lines at a big font size")
}

With alignment: .top the multi-line text top-aligns with the image.

Color contrast

The default SwiftUI colours meet Apple’s accessibility contrast standard. If you use custom colours, test them:

WCAG AA: 4.5:1 contrast ratio for normal text, 3:1 for large text.

Check colour pairs in Figma, Sketch, or an online tool. Pay special attention to brand colours (light gray on white that the user can’t read).

In SwiftUI, Color.primary / Color.secondary are safe defaults. They adapt to light and dark mode on their own.

Focus state (navigation)

Focus state matters for keyboard users. Which element is selected has to be visible:

Button("Option 1") { }
    .focusable(true)
    .focused($isFocused)

SwiftUI buttons show a focus ring by default. On custom UI you draw the focus state yourself.

Users on iPad with an external keyboard, Apple TV users, and switch control users all depend on focus state.

Reduce Motion

Some users are bothered by motion animations (motion sickness, vestibular sensitivity). Settings > Accessibility > Motion > Reduce Motion.

Your app has to check and respect the setting:

@Environment(.accessibilityReduceMotion) var reduceMotion

// ...
.animation(reduceMotion ? nil : .easeOut, value: state)

If Reduce Motion is on, you either disable or soften the animation. In UIKit it’s the UIAccessibility.isReduceMotionEnabled flag.

Dark Mode and High Contrast

Dark mode is handled in SwiftUI by default. Test custom colours.

High Contrast mode (Settings > Accessibility > Display > Increase Contrast) boosts foreground/background contrast. Default colours auto-adapt. For custom colours, define high-contrast variants via .preferredColorScheme.

Testing

Two ways to test accessibility:

1. Accessibility Inspector (Xcode > Open Developer Tool). Inspect the UI in the simulator. What’s each element’s accessibility label? Any missing labels?

2. Turn VoiceOver on for real. Settings > Accessibility > VoiceOver. Use your app for 10 to 15 minutes with VoiceOver. Incredibly eye-opening.

3. Test at large Dynamic Type. In the simulator: Environment Override > Text Size > Accessibility XXX Large. Is the app still readable, does the layout break?

4. Reduced Motion test. Turn it on in the simulator and use the app.

Those four should be your minimum discipline before every release.

Accessibility audit checklist

Questions I ask on PR review:

  • [ ] Does every image have a meaningful accessibilityLabel or accessibilityHidden = true?
  • [ ] Does every button or interactive element have an accessibilityLabel and, where useful, an accessibilityHint?
  • [ ] Are fonts system sizes, and if custom do they support Dynamic Type?
  • [ ] Any fixed-height buttons? Do they break with multi-line text?
  • [ ] Do custom colours pass the accessibility contrast bar?
  • [ ] Do animations respect Reduce Motion?
  • [ ] Has Accessibility Inspector + VoiceOver testing been done for at least 5 minutes?

These seven items get you to roughly 80% accessibility quality.

Apple’s accessibility assets

WWDC has tons of accessibility sessions. Even generic ones like “SF Symbols in Your App” include accessibility tips.

Apple’s Accessibility Sample Code is an excellent reference. Accessibility Inspector is a built-in tool.

iOS 18 brought a lot more accessibility API: Music Haptics, Live Captions, Eye Tracking, Vocal Shortcuts. The pace of change is aggressive, it’s worth tracking.

Business case

A meaningful slice of your user base uses these features:

  • Dynamic Type at a larger size: 15 to 20%
  • VoiceOver daily: 1 to 2%
  • Reduce Motion: 3 to 5%
  • High Contrast: 2 to 3%

Put together, that’s 25 to 30% of your users. A serious audience. Ignoring accessibility is giving that audience up.

In many countries it’s also a legal requirement. ADA compliance in the US, EN 301 549 in the EU, and similar regulations elsewhere.

Takeaway

Accessibility isn’t an afterthought, it’s a baseline. 10 to 15 minutes of VoiceOver plus Accessibility Inspector testing on every release. Checklist discipline on PR review.

That minimum investment gets you to an accessibility-friendly app, and you iterate on the advanced scenarios later. You don’t need to be perfect on day one, but the baseline has to be there.

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch