Skip to content
AF
Accessibility

TalkBack and the Android workout screen

Last verified August 22, 2026 · 7 min read

A TalkBack user meets a workout screen as a sequence of stops, and the quality of that sequence is decided by the semantics you attach. Google's documentation states that semantic properties convey the meaning of the corresponding composable, describes contentDescription as conveying in text what the meaning of an icon is, and describes the state description as how the On state should be referenced, which can be made more specific based on the context. An exercise row should be one stop rather than four: Google documents that calling Modifier.semantics with mergeDescendants set to true indicates the semantics properties should be merged, and its principles page says consolidating related elements helps users of assistive technology discover the information on the screen more efficiently. For the alert-like moment when rest ends, Google documents the liveRegion semantics property, with LiveRegionMode.Polite in the documented example, allowing accessibility services to automatically notify the user of changes to that component or its children. This is what Google's documentation states rather than device behaviour we measured, and two things it does not cover are an Android equivalent of Reduce Motion and how Voice Access or Switch Access behave.

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 elementWhat to attachWhat the user should hear
Exercise row in a planmerged descendantsone stop naming the exercise, sets and target
A single set rowmerged descendants plus a state descriptionthe set, its reps and weight, then completed or not
Rest timera polite live region for the finished momentrest finished, next exercise
Next-exercise cardmerged descendantsthe card as one stop, naming the exercise it advances to
Icon-only pause buttona content descriptionwhat the button does, not what the glyph looks like
Weight stepperdescriptions on each control, not mergedincrease 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