iOS Accessibility Testing: A Hands-On Guide for Developers and QA Teams
Apple’s tooling has improved significantly over the years, and automated audits can quickly identify issues like missing accessibility labels, insufficient touch target sizes, or low color contrast. But they can’t tell you whether VoiceOver users can complete a checkout flow, or whether a custom SwiftUI component behaves as users expect.
That’s because accessibility on iOS is built around user interactions, not just UI properties. In my experience, I use automated accessibility audits in Xcode to catch the obvious issues first, then switch to a real iPhone to test the app with VoiceOver, Larger Text, and other accessibility settings enabled. In this guide, I’ll walk through:
- What makes iOS accessibility testing different
- How I test accessibility on iPhones step by step
- The tools I rely on
- Mistakes I’ve made that changed the way I approach iOS accessibility testing
What Makes iOS Accessibility Testing Unique
While accessibility guidelines like WCAG apply across platforms, testing an iOS app comes with its own set of considerations. Understanding these differences helps you choose the right testing approach and avoid issues that automated scans often miss.
#1: Accessibility Is Built Into UIKit and SwiftUI
Unlike some platforms where accessibility requires extensive configuration, iOS provides native accessibility APIs for almost every UI component. Standard controls like buttons, text fields, switches, and lists automatically expose semantic information to assistive technologies. Developers mainly need to provide meaningful labels, hints, values, and traits where appropriate.
For example, an icon-only button should expose a descriptive accessibility label instead of relying on the visual icon alone.
Button(action: addToFavorites) {
Image(systemName: “heart.fill”)
}
.accessibilityLabel(“Add to Favorites”)
When developers build custom controls or combine multiple views into a single interactive element, these semantics often need to be configured manually. This is where I see many accessibility issues introduced.
#2: VoiceOver Tests More Than Whether Elements Are Read Aloud
VoiceOver is Apple’s built-in screen reader, but accessibility testing goes far beyond checking whether it announces a button’s label. I typically verify whether focus moves in a logical order, controls expose the correct role (Button, Header, Image, etc.), swipe navigation feels natural, and related elements are grouped correctly. I also test common VoiceOver gestures like the Rotor to ensure headings, links, or other navigational elements work as expected.
These interaction issues rarely appear in automated reports but can significantly impact usability for screen reader users.
#3: Dynamic Type Can Break Otherwise Well-Designed Screens
Supporting Dynamic Type isn’t just about allowing users to increase font sizes. Every screen should remain readable and functional when text is scaled to the largest accessibility sizes. During testing, I look for truncated text, overlapping controls, clipped buttons, broken Auto Layout constraints, and horizontal scrolling that wasn’t intended.
Many layouts look perfect at the default font size but become unusable once accessibility text sizes are enabled.
#4: SwiftUI Makes Accessibility Easier, But Not Automatic
SwiftUI automatically assigns accessibility information to many built-in controls, reducing the amount of manual work developers need to do. However, custom layouts built using stacks, overlays, or multiple nested views can still produce confusing accessibility hierarchies.
For example, combining an icon and text into a single actionable element often requires explicitly grouping child elements.
HStack {
Image(systemName: “star.fill”)
Text(“Featured”)
}
.accessibilityElement(children: .combine)
Whenever I test a SwiftUI app, I verify the accessibility hierarchy in addition to the visual UI. A screen that looks intuitive doesn’t always translate into an intuitive experience for VoiceOver users.
How to Test Accessibility with an iPhone: Step-by-Step Guide (Coffee Shop App)
Automated scanners and simulators can only take you so far. To understand the actual user experience, you need to un-tether your test device from your Mac.
Why a real device is non-negotiable: The Simulator can approximate VoiceOver, but it can’t replicate the physical realities that actually break an experience: the weight and reach of holding a phone one-handed, the timing quirks of real touch input, the way Dynamic Type interacts with the actual rendering engine, or how a screen reader user’s swipe gestures feel under their thumb.
Before you flip the switch on any assistive technology, set up the Accessibility Shortcut first. Go to Settings > Accessibility > Accessibility Shortcut (at the very bottom) and check VoiceOver and Dynamic Type. Now, you can triple-click the side button to instantly toggle these tools on or off without getting trapped in a UI.
Here is the exact step-by-step protocol, with a Coffee Shop App as an example. Every check is written assuming a real iPhone in your hand, not a Simulator window on your Mac.
Step 1: Audit Dynamic Type and Layout Flexibility
Many layouts look pristine at standard settings but break when a user increases text size. In our coffee app, a long order name shouldn’t push the “Checkout” button off the screen or cut off the price.
How to Test: Go to Settings > Accessibility > Display & Text Size > Larger Text. Turn on Larger Accessibility Sizes and drag the slider to the maximum. Return to the app and check your menus and checkout screens.
The Fix: Avoid hardcoded heights. Allow text to wrap vertically.
// ❌ Fixed height causes text truncation
Text(“Double Shot Vanilla Oat Milk Latte”)
.frame(height: 30)
// ✅ Allows text to wrap and expand layout naturally
Text(“Double Shot Vanilla Oat Milk Latte”)
.fixedSize(horizontal: false, vertical: true)
Step 2: Run the VoiceOver “Smoke Test”
Screen reader users navigate an app linearly by swiping right to move to the next item. If VoiceOver reads the coffee illustration, the title, and the price as three separate, disjointed stops, the experience feels clunky and slow.
How to Test: Triple-click your side button to turn on VoiceOver. Swipe right continuously to advance focus from top to bottom. Verify that focus moves logically and that related details are grouped together.
The Fix: Hide decorative illustrations and combine text blocks into a single focus target.
Step 3: Test Touch Target Sizes and Hit Zones
Users with motor impairments or those operating a device one-handed struggle to tap tiny buttons. In our app, adding or removing an item from the cart shouldn’t require surgical precision. Apple’s guidelines require a minimum target of 44x44 points.
How to Test: Try to tap the small “Delete” or “Edit Item” icons quickly or slightly off-center. If you accidentally trigger the wrong item or nothing happens at all, the hit zone is too small.
The Fix: Increase the button’s internal padding without altering its visual size.
// ❌ Hit target is restricted to the tiny 16x16 icon
Button(action: removeCoffee) {
Image(systemName: “xmark.circle”).frame(width: 16, height: 16)
}
// ✅ Tappable area safely meets the 44x44 point rule
Button(action: removeCoffee) {
Image(systemName: “xmark.circle”).padding(14)
}
.contentShape(Rectangle()) // Makes the transparent padding tappable
Step 4: Validate Navigation via the VoiceOver Rotor and Custom Actions
The VoiceOver Rotor acts like an index, letting power users skip sequential swiping and jump straight to headings or perform quick tasks on a list item.
Get Sean Wintermere’s stories in your inbox
Join Medium for free to get updates from this writer.
How to Test: With VoiceOver on, make a “turning a dial” gesture with two fingers to select Headings. Swipe down with one finger to jump directly between menu sections (e.g., from Espressos to Pastries). On the cart item, swipe up or down to see if you can quickly delete an item without opening a secondary edit menu.
The Fix: Mark section titles as headers and add native accessibility actions to list rows.
What to Look for in an iOS Accessibility Testing Tool
Manual testing catches what tools miss, but you still need tooling; the manual pass above doesn’t scale across every screen, every release, every regression. Here’s what actually mattered to my team:
- It integrates into CI, not just ad-hoc runs. If checking accessibility means someone has to remember to open a separate app, it will get skipped the week before a release.
- It flags actionable issues, not just raw contrast ratios and label counts. A tool that dumps many warnings with no severity or context trains your team to ignore the report entirely.
- It understands custom accessibility APIs, not just default UIKit/SwiftUI controls. Our order-customization slider and swipe-to-delete cart row are both custom; a tool that only checks default components would give us a false “all clear.”
- It produces a diffable report. We want to know what regressed between builds, not just what’s currently broken.
None of this replaces the manual VoiceOver walkthrough from the steps above.
iOS Accessibility Testing Tools I Use
Apple’s Accessibility Inspector (Local Checks)
Apple’s built-in tool, bundled with Xcode. This is where I start on any new screen, because it lets you hover over an element and see exactly what VoiceOver will announce: label, value, traits, and hint, without touching a physical device.
- How I use it: Open Xcode > Open Developer Tool > Accessibility Inspector, point it at the Simulator or a connected iPhone, and run the built-in Audit on the checkout screen. It catches missing labels and low contrast automatically.
- Where it falls short: It won’t tell you if the reading order feels wrong to a real VoiceOver user, or if a Rotor gesture actually works the way you expect.
XCUITest Framework (for CI/CD Automation)
Since Xcode 15, XCUIApplication supports performAccessibilityAudit(), which means we can bake accessibility checks directly into our existing UI test suite and catch regressions on every pull request, not just before release.
- How I use it: We leverage performAccessibilityAudit() directly within our automated XCUITest suites. This allows us to bake accessibility checks directly into our regression pipeline to catch issues on every pull request before release.
func testCartScreenAccessibility() throws {
let app = XCUIApplication()
app.launch()
app.buttons[“View Cart”].tap()
// ✅ GOOD: Fails the build if contrast, labels, or hit targets regress
try app.performAccessibilityAudit()
}
- Where it falls short: It audits what’s on screen at that instant. It won’t catch a Dynamic Type layout break unless you explicitly drive the test with larger accessibility sizes set first.
BrowserStack App Accessibility (for Device Cloud Testing)
For real-device coverage across iPhone models we don’t own: older SE-size screens, various iOS versions, we run our build through BrowserStack’s accessibility scanning rather than trying to maintain a device cloud ourselves.
- How I use it: Upload the build before a release cut and run it against a matrix of physical devices, then cross-reference flagged issues with the manual VoiceOver Smoke Test above.
- Where it falls short: It’s still an automated scan under the hood. It’s excellent at scale and device coverage, but it doesn’t replace someone actually swiping through the coffee menu with VoiceOver on.
iOS Accessibility Testing Mistakes I’ve Made (and How I Fixed Them)
Mistake 1: Treating a “Passed” Accessibility Audit as Done
One of the biggest mistakes I made early on was assuming that a clean Accessibility Inspector audit meant the screen was accessible. Automated audits are excellent at finding missing labels, contrast issues, and other implementation problems, but they can’t tell you whether someone using VoiceOver can actually complete a task.
We eventually discovered screens where VoiceOver jumped between elements in a confusing order, making forms, lists, or checkout flows difficult to navigate even though every element technically had an accessibility label.
// ❌ Every element is accessible, but the reading order
// doesn’t match the visual flow of the interface.
ZStack {
priceLabel
titleLabel
}
// ✅ GOOD: Explicitly tells VoiceOver to read the title before the price
ZStack {
priceLabel .accessibilitySortPriority(1)
titleLabel .accessibilitySortPriority(2)
}
The fix: Every feature now includes a manual VoiceOver walkthrough before it’s considered complete. If a user can’t navigate the screen naturally using swipe gestures alone, we treat it as a bug regardless of what automated tools report.
Mistake 2: Only Testing the “Happy Path” Accessibility Settings
For a long time, we verified Dynamic Type at one larger text size, checked Dark Mode, and moved on. We weren’t testing the extreme settings that many users actually rely on.
That’s where issues started appearing:
- Buttons moved off-screen at the largest accessibility text sizes.
- Labels overlapped input fields.
- Horizontal layouts became unusable.
- Important controls were clipped inside fixed-height containers.
The fix: Our accessibility checklist now always includes testing with:
- Largest Accessibility Dynamic Type size
- VoiceOver enabled
- Bold Text enabled
- Increase Contrast enabled
- Different device sizes (especially smaller iPhones)
Those combinations expose layout bugs that standard UI testing rarely catches.
Mistake 3: Forgetting Accessibility After UI Changes
Accessibility regressions often appear during seemingly harmless UI updates. A designer changes a layout, a developer replaces a button with a custom component, or a new icon is added, and suddenly labels disappear or focus order changes.
The feature still works, so the regression often slips through code review.
The fix: We include accessibility checks in every UI-related pull request and run automated accessibility audits in CI. Any screen with visual changes also gets a quick manual verification before release.
Final Thoughts: Building an Accessibility-First iOS Testing Strategy
The biggest shift in my team’s approach wasn’t adopting another accessibility tool. It was changing when and how we tested.
We now treat accessibility like any other quality gate. Every new screen gets a quick Accessibility Inspector audit during development, every feature is manually tested with VoiceOver before it’s merged, and responsive layouts are verified at the largest Accessibility Dynamic Type size instead of the default text settings. That workflow catches the majority of accessibility issues before they ever reach QA.
Most importantly, don’t wait until an accessibility audit before release. If developers run automated checks while building the feature and QA validates real user journeys with VoiceOver during testing, accessibility becomes another part of the development workflow.









