Automated checks find the missing labels. They can't tell you the label is wrong.
Compose's accessibility checks catch touch targets, contrast and missing labels automatically. The failures they cannot detect — wrong labels, bad order, gesture-only actions — need a person, and knowing the split makes both cheaper.

Day 70 of 100, closing the accessibility pillar. There's a real answer to "can we automate this", and it's neither yes nor no.
The symptom
A codebase that passes every accessibility lint and is still unusable with TalkBack:
IconButton(onClick = ::delete) {
Icon(Icons.Default.Delete, contentDescription = "Icon")
}
Every check passes. There's a content description, the touch target is 48dp, the contrast is fine. And the announcement is "Icon, button" — which tells the user nothing about what pressing it will do, on a control that deletes their data.
The lint verified that a string exists. Nobody verified that it means anything.
Why the obvious approach fails
The obvious approach is to lean entirely on automation:
@Test fun screenIsAccessible() = runComposeUiTest {
setContent { InboxScreen(sample) }
enableAccessibilityChecks()
onRoot().tryPerformAccessibilityChecks()
}
That's a genuinely useful test and it establishes a floor, not a ceiling. Automated checks verify properties of the tree — a description exists, a target is big enough, a contrast ratio passes. They cannot verify meaning, because meaning isn't a property they can compute.
The opposite failure is equally common: a team that knows automation is insufficient concludes accessibility can't be tested, and does neither.

The actual mechanism
Compose ships accessibility checks that run inside ordinary UI tests:
@Test fun inboxPassesAccessibilityChecks() = runComposeUiTest {
setContent { AppTheme { InboxScreen(sampleMessages) } }
enableAccessibilityChecks()
onRoot().tryPerformAccessibilityChecks()
}
These wrap the Accessibility Test Framework, and what they catch is worth knowing precisely:
- Touch target size — anything below 48×48dp.
- Contrast — text and background below the WCAG ratio.
- Missing labels — clickable nodes with no description and no text.
- Duplicate descriptions — two nodes claiming the same label.
- Traversal problems — some ordering issues, though not all.
Adding that to a handful of screen-level tests is maybe an hour of work and catches the majority of structural failures permanently — including regressions, which is the part manual testing can't provide.
What automation cannot see
Four categories, and each one is a real bug an automated suite passes:
A wrong label. contentDescription = "Icon" satisfies every check. So does a label
that's accurate but useless ("Button"), one that's stale after a redesign, or one that
describes appearance rather than function.
A bad reading order. Day 69's FAB-after-two-hundred-messages passes structurally — every node is labelled, every target is big enough. It's simply unusable.
A gesture-only action. Swipe-to-archive with no custom action is invisible, and nothing flags it, because the checks see a well-formed list.
State that's only visual. A selected chip that changes colour and doesn't set
stateDescription announces identically whether selected or not. Contrast passes; the
information is missing.
Those four are most of what makes an app genuinely hard to use, and none is detectable without judgement.
The twenty-minute manual pass
The manual half is smaller than people expect, and it's a checklist rather than an art:
1. Turn on TalkBack and swipe through the screen. Left-to-right, every element. Ask: does each announcement tell me what the thing is and what it does?
2. Count the swipes. Compare against how many pieces of information the screen holds. Four swipes per list item means Day 68's merging isn't happening.
3. Check the order. Does it match how you'd describe the screen aloud?
4. Perform every action without touching a control directly. Use TalkBack's double-tap and its action menu. Anything only reachable by a gesture is a Day 67 failure.
5. Turn font scale to 200% and check nothing clips. Day 46's preview catches this earlier, but the device is the proof.
Twenty minutes per screen the first time, five thereafter. Doing it on the screens you build, as you build them, is dramatically cheaper than an audit six months later.
Where the two meet
The productive division:
Automate the floor. Screen-level accessibility checks in CI, so structural regressions fail the build. These are cheap to add and never get worse.
Assert the specifics you care about. A test that the delete button announces something containing "delete" is a real test, and it catches the "Icon" case that generic checks can't:
@Test fun deleteButtonIsDescribed() = runComposeUiTest {
setContent { MessageRow(sample, onDelete = {}) }
onNodeWithContentDescription("Delete message").assertExists()
}
Writing that assertion forces you to decide what the label should say, which is most of the value.
Manual-check the meaning, on the screens you're changing, before the PR.
The third leg, easy to forget: use real assistive technology occasionally on the whole app, not just the screen in front of you. Individually-correct screens can still add up to a flow that can't be completed — a sign-up where step three's continue button is reachable but announced as "Button", or a checkout whose error message never gets focus.
For that last case specifically, liveRegion is the tool:
Text(
error,
modifier = Modifier.semantics { liveRegion = LiveRegionMode.Assertive },
)
A validation error that appears silently is invisible to a screen-reader user, who has no
reason to swipe back up looking for it. Polite waits for a pause; Assertive
interrupts. Errors are assertive; status updates are polite.
The reframe worth making
Accessibility work concentrates in exactly the places the earlier pillars already flagged as needing care — custom controls, icon-only buttons, gesture-driven interactions, information conveyed by colour alone. Those are the same places that need extra tests, extra documentation and extra design review for every audience.
Which suggests the practical framing: this isn't a separate quality axis. A screen that's hard to describe is usually a screen whose structure is unclear, and clarifying it improves the visual design too. That's not a moral argument, it's an observation about where the work lands.
How to prove it
Take a screen you believe is accessible and do the five-step pass. The first time, most people find two or three genuine problems on a screen that passes every automated check — and that gap, measured once on your own code, is the argument for keeping both halves.
Then add the automated check to that screen's test and watch it stay fixed.
The measurement worth taking once: how long the five-step pass takes on a screen you wrote this week, versus a screen from a year ago. The difference is the cost of deferring, and it is usually large enough to settle the argument about when this work belongs.
What this generalizes to
The pillar's conclusion: automation verifies form; people verify meaning. A tool can
confirm a description exists and cannot confirm it's the right description, in the same
way a type checker confirms a function returns a String and not that the string is
correct.
Four days of accessibility come down to a small set of ideas — there is a second tree, its shape should match the content rather than the code, its order is a separate requirement from layout, and its correctness is half machine-checkable. The APIs are few; the discipline is the part that has to be built in rather than added afterwards. The APIs are few; the discipline is the part that has to be built in rather than added.
Tomorrow, Day 71 opens the gestures pillar.
Day 70 of a 100-day series on Jetpack Compose, working through the official documentation in order. Source: Test accessibility.