TalkBack and the Android workout screen
Last verified August 22, 2026 · 7 min read
A TalkBack user does not see your workout screen as a layout. They meet it as a sequence of stops, in an order you did not consciously choose, each announcing whatever text the framework could find. A set row built from four composables is four stops. An icon button with no description is a stop that says nothing useful. The rest timer, which is the one element the user is waiting on, is silent unless you asked for it to speak.
None of that is fixed by adding text. It is fixed by attaching semantics: the parallel description of the screen that accessibility services read. This page is engineering guidance and makes no legal claims; law and store policy live in compliance.
Semantics are the screen the service reads#
Google's Compose semantics guide puts it in one line: "Semantic properties convey the meaning of the corresponding composable." Everything below sets those properties on the elements a training screen is made of.
Two do most of the work on a workout screen. The first describes what a thing is; Google's documentation gives the icon case directly: "An Icon contains a contentDescription property (if set by the developer) that conveys in text what the meaning of the icon is." The second is the description of what state a thing is in. On that, Google writes: "The StateDescription describes how the 'On' state should be referenced. By default this is a localized version of the word 'On', but this can be made more specific (for example, 'Enabled') based on the context."
That last clause is the useful one for us. A completed set is a state, not a name. The row is still "set two, ten reps at sixty kilograms" after the user finishes it; what changed is a state that can be described in your own words rather than the framework's default.
Modifier.semantics { contentDescription = "Start rest timer" }
Modifier.semantics { stateDescription = "Completed" }
One stop, not four#
An exercise row usually renders as a name, a set number, a rep target and a weight, each its own composable. Left alone that is four destinations, and reaching the weight means passing three stops that made no sense in isolation.
Google documents the fix: "Composables and modifiers can indicate that they want to merge their descendants' semantics properties by calling Modifier.semantics (mergeDescendants = true) {}. Setting this property to true indicates that the semantics properties should be merged." Google's accessibility principles page gives the reason in plain terms: "Consolidating related elements helps users of assistive technology discover the information on the screen more efficiently," and "Setting mergeDescendants to true groups these inner elements, so accessibility services can treat them as one unit."
Modifier.semantics(mergeDescendants = true) {}
Where to draw the boundary is our judgement, not Google's. The unit that should be one stop is the unit a user makes one decision about. An exercise row is one decision — do I do this next. A set row is one decision — did I finish this. A weight stepper is not part of either, because plus and minus are separate actions that merging would remove.
| Workout screen element | What to attach | What the user should hear |
|---|---|---|
| Exercise row in a plan | merged descendants | one stop naming the exercise, sets and target |
| A single set row | merged descendants plus a state description | the set, its reps and weight, then completed or not |
| Rest timer | a polite live region for the finished moment | rest finished, next exercise |
| Next-exercise card | merged descendants | the card as one stop, naming the exercise it advances to |
| Icon-only pause button | a content description | what the button does, not what the glyph looks like |
| Weight stepper | descriptions on each control, not merged | increase and decrease as separate actions |
The rest timer and the live region#
A rest countdown has two jobs. While it runs it is a number the user may check. When it hits zero it is an event they need to be told about, because they are lying on a bench looking at the ceiling.
Google documents the mechanism for the second job: "Alert-like components can be marked with the liveRegion semantics property. This allows accessibility services to automatically notify the user of changes to this component, or its children". The documented example uses the polite mode.
Modifier.semantics { liveRegion = LiveRegionMode.Polite }
Two pieces of judgement, ours. Mark the alert, not the clock: a live region on a composable whose text changes every second is a decision to talk over the user continuously. Attach it to the component that appears when rest ends and leave the ticking number as an ordinary element the user can read on demand. And keep the count small, usually one per workout screen, because two competing alert-like components produce the same problem in a harder form. The same restraint applies to anything ongoing you put in the shade, covered in Wear OS ongoing activity.
Labelling the rest of the screen#
Google's principles page sets the bar in one sentence: "Users must be able to understand the content and purpose of each interactive and meaningful UI element within your app." The same page lists labelling elements and adding accessibility actions among its best practices, and is realistic about the manual work: "In most cases, Compose APIs and Material have default accessibility support in place. However, if you need to manually specify a UI element's semantic properties, use the semantics modifier and the contentDescription property."
Take that seriously in both directions. The defaults mean a Material button with a text child is usually not your problem. They also mean the elements a fitness app invents — the plate diagram, the muscle-group heat map, the pose overlay from rep counting — have no default at all, because Material never heard of them. Those are the elements teams forget, and they usually carry the product's actual value. The surrounding structure for the Android build is in AI workout tracking on Android with Kotlin and, for the wrist, the Wear OS app anatomy.
What we did not verify#
Three gaps, stated rather than papered over.
We did not verify an Android equivalent of Reduce Motion. Apple documents a setting and a flag for reduced motion; for Android we did not find and read a primary page describing the animation-scale behaviour, so this page says nothing about how to detect it or what to do when it is on. Do not infer the Android behaviour from the Apple one.
We did not verify how Voice Access or Switch Access behave. What we can state comes from Google's accessibility service documentation, which says these services "run in the background and communicate with the system to inspect screen content and interact with apps on the user's behalf. Examples include screen readers (like TalkBack), Switch Access tools, and voice control systems." That such services exist is the whole of our claim. Anything about how a switch user moves through a set row, or what a spoken command does to your pause button, is not on this page because we could not source it.
And nothing here is measured. These are the semantics Google's documentation describes, not observations of TalkBack on a device in a gym. Google's own caution is worth repeating: "An accessibility service is a specialized tool, not a standard way to make your app accessible." Building your own service is not the fix for a screen with no semantics on it.
Monday morning#
Take one screen — the live workout screen, not the settings list — and count its stops. If a set row is more than one, merge it. Find every icon-only control and write a description of what it does, not what it depicts. Give the rest-finished component a polite live region and check nothing else on the screen has one. Last, list the custom-drawn views your product invented: those have no default behind them, and each needs a decision about what it says. Getting real devices into that loop is device lab and CI.
Frequently asked questions
- Should every cell of a set row be its own TalkBack stop?
- Usually not. Google documents that composables and modifiers can indicate they want to merge their descendants' semantics properties by calling Modifier.semantics (mergeDescendants = true) {}, and that setting it to true means the semantics properties should be merged. The principles page adds that consolidating related elements helps users of assistive technology discover the information on the screen more efficiently, and that merging groups inner elements so accessibility services can treat them as one unit. Our rule of thumb: merge whatever the user makes a single decision about. Keep separate controls, such as a weight stepper's increase and decrease, unmerged, because merging removes them as actions.
- Which Compose semantics property should a rest-finished alert use?
- The live region property. Google documents that alert-like components can be marked with the liveRegion semantics property, which allows accessibility services to automatically notify the user of changes to this component or its children, and the documented example uses LiveRegionMode.Polite. Attach it to the component that appears or changes when rest ends, not to the ticking countdown itself. A live region on a value that changes every second is a decision to speak continuously. Our judgement is one live region per workout screen; a second competing alert-like component creates the same interruption problem in a form that is harder to debug.
- Does Jetpack Compose label a workout screen's custom views for me?
- Only the standard parts. Google states that in most cases Compose APIs and Material have default accessibility support in place, and that if you need to manually specify a UI element's semantic properties you use the semantics modifier and the contentDescription property. A Material button with a text child is generally handled. The elements a fitness app invents are not: a plate diagram, a muscle heat map, a pose overlay, a chart drawn on a canvas. Those carry no default because the framework has no idea what they represent, and they are usually the parts carrying the product's real information.
Keep reading
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed August 22, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All accessibility · by AIFitnessAPI