Skip to content
All posts
March 6, 20264 min read

Accessibility Testing for Android Apps: The Developer's Guide

Accessibility testing is often treated as optional. It's not — it's good engineering that serves millions of users with disabilities. Here's how to test and fix the most common accessibility issues in Android apps.

AndroidTestingKotlin
Share:

Over 1 billion people have a disability. Many of them use Android apps. If your app doesn't work with TalkBack or breaks at large font sizes, you've cut those users out.

This isn't charity. It's engineering quality. And in many markets, it's required by law.


The Three Levels of Accessibility Testing

  1. Automated static analysis — catches missing content descriptions, small touch targets, low contrast
  2. TalkBack testing — manual testing with the screen reader enabled
  3. User testing — testing with actual users who rely on assistive technology

Most teams skip all three. Start with automated static analysis — it takes 10 minutes.


Step 1: Accessibility Scanner

Google's Accessibility Scanner is a free app that overlays your app with accessibility issues.

Install it. Run your app. Tap "Check." You'll see:

  • Elements missing content descriptions
  • Elements with touch targets smaller than 48x48dp
  • Text with insufficient color contrast

Fix everything flagged here before moving to manual testing.


Step 2: Content Descriptions

Every interactive element needs a content description for TalkBack users:

kotlin
// Bad — TalkBack says "unlabeled button"
IconButton(onClick = { deleteTask(task) }) {
    Icon(Icons.Default.Delete, contentDescription = null) // ← null is wrong
}

// Good — TalkBack says "Delete task Buy groceries"
IconButton(
    onClick = { deleteTask(task) },
    modifier = Modifier.semantics {
        contentDescription = "Delete task ${task.title}"
    }
) {
    Icon(Icons.Default.Delete, contentDescription = "Delete")
}

Images that are purely decorative should have

code
contentDescription = null
with
code
Role.Image
and
code
invisibleToUser()
— so TalkBack skips them:

kotlin
Image(
    painter = painterResource(R.drawable.decorative_background),
    contentDescription = null, // Decorative — screen reader skips it
    modifier = Modifier.semantics { invisibleToUser() }
)

Step 3: Touch Target Sizes

The minimum touch target is 48x48dp. Small icons (16-24dp) need padding to meet the minimum:

kotlin
Icon(
    imageVector = Icons.Default.MoreVert,
    contentDescription = "More options",
    modifier = Modifier
        .size(24.dp)
        .padding(12.dp) // Total touch area: 48dp
        .clickable { showMenu() }
)

Or use

code
minimumInteractiveComponentSize()
in Compose:

kotlin
// Automatically ensures 48dp minimum touch target
IconButton(onClick = { showMenu() }) {
    Icon(Icons.Default.MoreVert, contentDescription = "More options")
}

code
IconButton
already handles this. Custom touchable elements often don't.


Step 4: Color Contrast

WCAG AA requires:

  • Normal text (< 18pt): 4.5:1 contrast ratio
  • Large text (≥ 18pt bold / ≥ 24pt regular): 3:1 contrast ratio
  • UI components and icons: 3:1 contrast ratio

Check your color palette at contrast-ratio.com.

In your theme:

kotlin
// Design system colors with contrast verified
val TextPrimary = Color(0xFFE8E8E8)  // Verified 12:1 against dark background
val TextSecondary = Color(0xFF9E9E9E) // Check this — might be borderline
val Accent = Color(0xFFFFA400)        // Verified 4.7:1 against dark

[!WARNING] "It looks fine to me" is not a contrast check. Use a tool. Developers without color vision deficiencies regularly ship low-contrast text that's unreadable to 8% of users.


Step 5: TalkBack Manual Testing

Enable TalkBack: Settings → Accessibility → TalkBack → On

Then test your critical flows:

  1. Navigate to your app's main screen
  2. Swipe right to move through elements — each element should be announced clearly
  3. Double-tap to activate buttons and links
  4. Navigate through forms — each input should announce its label and current value
  5. Navigate lists — items should be announced with meaningful descriptions

Common TalkBack issues:

  • Grouped elements being announced separately (use
    code
    mergeDescendants = true
    )
  • Custom components not announcing their role
  • Actionable items not being focusable
kotlin
// Group related elements into one TalkBack announcement
Row(
    modifier = Modifier.semantics(mergeDescendants = true) {}
) {
    Text(text = task.title)
    Text(text = if (task.completed) "Completed" else "Active")
}
// TalkBack announces: "Buy groceries, Completed" as one item

Step 6: Font Scaling

Test with system font size at maximum (usually 200%):

Settings → Display → Font size → Largest

Check for:

  • Text overflow (text cut off or overlapping)
  • Buttons that become too large and clip layout
  • Fixed-height containers that don't expand with text

Use

code
sp
for text sizes (scales with system font) and avoid fixed heights for text containers:

kotlin
// Good — wraps to accommodate large text
Column {
    Text(
        text = task.title,
        fontSize = 16.sp,  // sp scales with system font
        overflow = TextOverflow.Ellipsis,
        maxLines = 2
    )
}

// Bad — fixed height clips large text
Box(modifier = Modifier.height(40.dp)) {
    Text(text = task.title, fontSize = 16.sp)
}

Automated Accessibility Tests

Compose provides semantics testing:

kotlin
@Test
fun `delete button has meaningful content description`() {
    composeTestRule.onNodeWithContentDescription("Delete task Buy groceries")
        .assertExists()
        .assertHasClickAction()
}

@Test
fun `task item announces both title and status`() {
    composeTestRule.onNodeWithText("Buy groceries").assertExists()
    composeTestRule.onNodeWithText("Completed").assertExists()
}

Takeaways

  • Accessibility Scanner takes 10 minutes and catches the most obvious issues — run it on every release
  • Every interactive element needs a meaningful content description
  • Touch targets must be at least 48x48dp — use
    code
    IconButton
    which handles this automatically
  • Check color contrast with a tool, not your eyes
  • Test with TalkBack enabled for your top 3 user flows — it reveals issues static analysis misses
Share:
S

Sudarshan Chaudhari

AI Systems Builder / Product Engineer

Bangkok, Thailand

Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

MyFamilyTracker

Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this