Accessibility in Mobile Apps: Building for All Users

Approximately 4.4 million Australians have a disability. That is roughly 18 percent of the population. Many of these people use smartphones daily, relying on assistive technologies to navigate apps. If your app is not accessible, you are excluding a significant portion of potential users.

Beyond the moral argument, accessibility is increasingly a legal consideration. The Australian Disability Discrimination Act 1992 and the Web Content Accessibility Guidelines (WCAG) establish expectations for digital accessibility. Major organisations have faced legal action for inaccessible digital products.

This guide covers practical techniques for building accessible mobile apps on iOS and Android. These are not theoretical principles but implementable code patterns that make a real difference.

Understanding Assistive Technologies

Screen Readers

VoiceOver (iOS) and TalkBack (Android) are the primary screen readers. They read aloud the content on screen, allowing visually impaired users to navigate by swiping between elements and tapping to activate.

When a user navigates to an element, the screen reader announces:

  1. The element’s label (what it is)
  2. Its trait/role (button, heading, image)
  3. Its value or state (selected, disabled, 50 percent)
  4. Any hint (double-tap to activate)

Switch Control

Users with limited motor control may use external switches to navigate. The switch moves focus sequentially through interactive elements. Every interactive element must be focusable and have a clear label.

Dynamic Type / Font Scaling

Users with low vision increase system font sizes. Your app must support this gracefully, reflowing layouts rather than clipping text.

Voice Control

iOS Voice Control and Android Voice Access let users operate their devices entirely by voice. Interactive elements need visible labels that match their accessibility labels.

Access

ibility Labels

Every interactive element and meaningful image needs an accessibility label. This is the most impactful single improvement you can make.

iOS (Swift/SwiftUI)

// UIKit
button.accessibilityLabel = "Create new project"
button.accessibilityHint = "Opens a form to create a new project"

profileImage.accessibilityLabel = "Sarah's profile photo"
profileImage.isAccessibilityElement = true

// SwiftUI
Button(action: createProject) {
    Image(systemName: "plus")
}
.accessibilityLabel("Create new project")
.accessibilityHint("Opens a form to create a new project")

Image("profile-sarah")
    .accessibilityLabel("Sarah's profile photo")

Android (Kotlin)

// View-based
button.contentDescription = "Create new project"

profileImage.contentDescription = "Sarah's profile photo"

// Jetpack Compose
IconButton(
    onClick = { createProject() },
    modifier = Modifier.semantics {
        contentDescription = "Create new project"
    }
) {
    Icon(Icons.Filled.Add, contentDescription = null)
}

React Native

<TouchableOpacity
  onPress={createProject}
  accessible={true}
  accessibilityLabel="Create new project"
  accessibilityHint="Opens a form to create a new project"
  accessibilityRole="button"
>
  <Icon name="plus" />
</TouchableOpacity>

<Image
  source={require('./profile-sarah.png')}
  accessible={true}
  accessibilityLabel="Sarah's profile photo"
/>

Label Writing Guidelines

Do: Be concise and descriptive. “Delete project” not “Button to delete this project.”

Do: Describe the action, not the appearance. “Search” not “Magnifying glass icon.”

Do: Include state information. “Favourite, selected” or “Task completed.”

Do not: Include the element type. Screen readers announce “button” automatically. “Delete project button” is redundant.

Do not: Use vague labels. “Button 1” or “Image” is useless.

Do not: Include “tap to” or “click to.” Screen readers provide their own interaction hints.

Sema

ntic Structure

Headings

Mark headings as headings. Screen reader users navigate by headings to skim content, similar to how sighted users scan visually.

// SwiftUI
Text("Project Details")
    .font(.title)
    .accessibilityAddTraits(.isHeader)

// UIKit
label.accessibilityTraits = .header
// Compose
Text(
    "Project Details",
    modifier = Modifier.semantics { heading() },
    style = MaterialTheme.typography.h5
)
// React Native
<Text
  style={styles.heading}
  accessibilityRole="header"
>
  Project Details
</Text>

Lists

Announce list context so users know they are navigating a list:

// SwiftUI: Lists are accessible by default
List {
    ForEach(projects) { project in
        ProjectRow(project: project)
    }
}
// VoiceOver announces "row 1 of 5" etc.

Group related elements so they are announced as a single unit:

// SwiftUI: Combine elements
HStack {
    Image(systemName: "checkmark.circle.fill")
    VStack(alignment: .leading) {
        Text("Design mockups")
        Text("Due tomorrow")
    }
}
.accessibilityElement(children: .combine)
// VoiceOver: "checkmark circle fill, Design mockups, Due tomorrow"

// Or provide a custom label:
.accessibilityElement(children: .ignore)
.accessibilityLabel("Design mockups, completed, due tomorrow")
// React Native: Group elements
<View
  accessible={true}
  accessibilityLabel="Design mockups, completed, due tomorrow"
>
  <Icon name="check-circle" />
  <Text>Design mockups</Text>
  <Text>Due tomorrow</Text>
</View>

Dynamic Ty

pe Support

iOS Dynamic Type

Support Dynamic Type so text scales with the user’s preferred size:

// SwiftUI: Use built-in text styles (automatic Dynamic Type support)
Text("Project Title")
    .font(.headline) // Scales automatically

Text("Last updated 3 hours ago")
    .font(.caption) // Scales automatically

// UIKit: Use preferred fonts
label.font = UIFont.preferredFont(forTextStyle: .headline)
label.adjustsFontForContentSizeCategory = true

For custom fonts:

// UIKit: Scale custom fonts
let customFont = UIFont(name: "Avenir-Medium", size: 16)!
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true

Layout considerations:

  • Use Auto Layout constraints that adapt to content size
  • Never set fixed heights on text containers
  • Allow text to wrap to multiple lines
  • Test at the largest accessibility text size (Accessibility Extra Extra Extra Large)

Android Font Scaling

Android scales text automatically when you use sp units. Ensure your layouts accommodate larger text:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="16sp"
    android:maxLines="3"
    android:ellipsize="end" />

In Compose:

Text(
    text = "Project Title",
    style = MaterialTheme.typography.h6, // Uses sp by default
    maxLines = 2,
    overflow = TextOverflow.Ellipsis
)

Colour and Contrast

Minimum Contrast Ratios

WCAG 2.1 Level AA requires:

  • Normal text: 4.5:1 contrast ratio
  • Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio
  • Non-text elements (icons, borders): 3:1 contrast ratio

Testing Contrast

iOS: Xcode’s Accessibility Inspector includes a colour contrast checker.

Android: Android Studio’s Layout Inspector shows accessibility warnings for insufficient contrast.

Online tools: WebAIM’s contrast checker (webaim.org/resources/contrastchecker) works for verifying specific colour pairs.

Do Not Rely on Colour Alone

Never use colour as the only way to convey information:

BAD:  Red text means error, green text means success (colour-blind users cannot distinguish)

GOOD: Red text with an error icon and "Error:" prefix. Green text with a checkmark and "Success:" prefix.
// Good: Icon + colour + text
HStack {
    Image(systemName: "exclamationmark.circle.fill")
        .foregroundColor(.red)
    Text("Error: Please enter a valid email")
        .foregroundColor(.red)
}
.accessibilityElement(children: .combine)

Dark Mode

Both iOS and Android support dark mode. Ensure your colour palette works in both modes:

  • Use semantic colours that adapt automatically
  • Test all screens in both light and dark mode
  • Verify contrast ratios in both modes
// SwiftUI: Adaptive colours
Text("Primary text")
    .foregroundColor(.primary) // Adapts to light/dark

Text("Secondary text")
    .foregroundColor(.secondary) // Adapts to light/dark

Touch Targets

Ensure all interactive elements meet minimum size requirements:

  • iOS: 44x44 points
  • Android: 48x48 dp
// SwiftUI: Ensure minimum touch target
Button(action: { /* action */ }) {
    Image(systemName: "trash")
        .font(.system(size: 16))
}
.frame(minWidth: 44, minHeight: 44)
// Compose: Ensure minimum touch target
IconButton(
    onClick = { /* action */ },
    modifier = Modifier.size(48.dp)
) {
    Icon(Icons.Default.Delete, contentDescription = "Delete")
}

Spacing between interactive elements should be at least 8 points to prevent accidental taps.

Motion and Animations

Some users experience motion sickness or vestigo from animations. Both platforms provide a “Reduce Motion” setting.

// SwiftUI: Respect reduce motion
@Environment(\.accessibilityReduceMotion) var reduceMotion

var body: some View {
    content
        .animation(reduceMotion ? .none : .easeInOut, value: isExpanded)
}

// UIKit
if UIAccessibility.isReduceMotionEnabled {
    // Use simple fade instead of slide animation
}
// Android: Check animation settings
val animationScale = Settings.Global.getFloat(
    contentResolver,
    Settings.Global.ANIMATOR_DURATION_SCALE,
    1f
)
if (animationScale == 0f) {
    // Animations are disabled
}

Form Accessibility

Forms are particularly important for accessibility. Poorly labelled forms are a common barrier.

Label Every Input

// SwiftUI
TextField("Email address", text: $email)
    .textContentType(.emailAddress)
    .keyboardType(.emailAddress)
    .accessibilityLabel("Email address")

Announce Errors Clearly

When form validation fails, announce the error to screen readers:

// iOS: Post accessibility notification
UIAccessibility.post(
    notification: .announcement,
    argument: "Error: Email address is invalid"
)
// React Native: Announce error
import { AccessibilityInfo } from 'react-native';

AccessibilityInfo.announceForAccessibility('Error: Email address is invalid');

Input Types

Set the correct keyboard type and content type for each field:

TextField("Phone number", text: $phone)
    .keyboardType(.phonePad)
    .textContentType(.telephoneNumber)

This helps all users, not just those using assistive technologies.

Testing Accessibility

Manual Testing

Test your app with each platform’s screen reader:

iOS VoiceOver:

  1. Enable in Settings, then Accessibility, then VoiceOver
  2. Navigate your entire app using swipe gestures
  3. Verify every element is announced meaningfully
  4. Complete all primary user tasks without looking at the screen

Android TalkBack:

  1. Enable in Settings, then Accessibility, then TalkBack
  2. Navigate using swipe and explore-by-touch
  3. Verify all elements are announced correctly
  4. Complete primary tasks using only TalkBack

Automated Testing

Use accessibility audit tools:

iOS: Xcode’s Accessibility Inspector scans for missing labels, insufficient contrast, and small touch targets.

Android: Android Studio’s Layout Inspector flags accessibility issues. The Accessibility Scanner app provides on-device audits.

Accessibility Audit Checklist

  • Every interactive element has a meaningful accessibility label
  • Headings are marked with the heading trait/role
  • Related elements are grouped appropriately
  • Dynamic Type is supported (text scales with system settings)
  • Colour contrast meets WCAG 2.1 AA requirements
  • Information is not conveyed by colour alone
  • Touch targets meet platform minimums (44pt iOS, 48dp Android)
  • Reduce Motion preference is respected
  • Form inputs are labelled and errors are announced
  • The app is fully navigable with a screen reader
  • Dark mode maintains adequate contrast

Making the Case for Accessibility

If you need to convince stakeholders that accessibility is worth the investment:

  1. Market size: 4.4 million Australians with disabilities represent a substantial addressable market
  2. Legal risk: The Disability Discrimination Act applies to digital products
  3. Broader benefits: Accessibility improvements (clear labels, good contrast, large touch targets) benefit all users
  4. App Store advantage: Apple highlights accessible apps, and users mention accessibility in positive reviews
  5. Retention: Users who rely on accessibility features are often highly loyal to apps that support them well

Accessibility is not a bolt-on feature. It is a quality of well-built software. At eawesome, we build accessibility into every mobile project from the start, because including all users is not a feature; it is a responsibility.