To assist users with accessibility needs, the Android framework lets you create an accessibility service that can present content from apps to users and also operate apps on their behalf.
Android provides several system accessibility services, including the following:
- TalkBack: helps people who have low vision or are blind. It announces content through a synthesized voice and performs actions on an app in response to user gestures.
- Switch Access: helps people who have motor disabilities. It highlights interactive elements and performs actions in response to the user pressing a button. It allows for controlling the device using only one or two buttons.
To help people with accessibility needs use your app successfully, your app must follow the best practices described on this page, which build on the guidelines described in Make apps more accessible.
Each of these best practices, described in the sections that follow, can further improve your app's accessibility:
- Label elements
- Users must be able to understand the content and purpose of each interactive and meaningful UI element within your app.
- Add accessibility actions
- By adding accessibility actions, you can enable users of accessibility services to complete critical user flows within your app.
- Use built-in accessibility features
- Compose offers many accessibility behaviors by default. Take advantage of predefined accessibility behaviors to make your components accessible with little or no additional work. Compose also provides ways to support more specific accessibility requirements not covered by the default features.
- Use cues other than color
- Users must be able to clearly distinguish between categories of elements in a UI. To do so, use patterns and position, along with color, to express these differences.
- Make media content more accessible
- Add descriptions to your app's video or audio content so that users who consume this content don't need to rely on entirely visual or aural cues.
Label elements
It's important to provide users with useful and descriptive labels for each interactive UI element in your app. Each label must explain the semantics of a particular element—that is, the element's meaning and purpose. Screen readers such as TalkBack can announce these labels to users.
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. For more information about semantics, see
Semantics.
The following sections describe several other labeling techniques.
Editable elements
When labeling editable elements, such as text fields, it's helpful to show text that gives an example of valid input in the element itself, in addition to making this example text available to screen readers. In these situations, you can use placeholder text, also called hint text.
In the following example, the TextField has a placeholder parameter that
provides hint text.
val usernameState = rememberTextFieldState() TextField( state = usernameState, lineLimits = TextFieldLineLimits.SingleLine, placeholder = { Text("Enter Username") } )
It's also common for a text field to have a corresponding descriptive label that describes what users must enter as input.
In the following example, the TextField has a label parameter that provides
an accessibility description.
TextField( state = rememberTextFieldState(initialText = "Hello"), label = { Text("Label") } )
For more information about text and user input, see Configure text fields.
Elements in a collection
When adding labels to the elements of a collection, each label must be unique. This way, the system's accessibility services can refer to exactly one on-screen element when announcing a label. This correspondence lets users know when they cycle through the UI or when they move focus to an element that they already discovered.
For example, when you have a LazyColumn or LazyRow, use the semantics
modifier to assign a unique collectionItemInfo to each item, as shown in the
following snippet:
MilkyWayList( modifier = Modifier .semantics { collectionInfo = CollectionInfo( rowCount = milkyWay.count(), columnCount = 1 ) } ) { milkyWay.forEachIndexed { index, text -> Text( text = text, modifier = Modifier.semantics { collectionItemInfo = CollectionItemInfo(index, 0, 0, 0) } ) } }
For more information about semantics properties for lists and grids, see List and item information.
Groups of related content
If your app displays several UI elements that form a natural group, such as
details of a song or attributes of a message, arrange these elements within a
parent container (like Column, Row, or Box). Use the parent container's
semantics modifier to set mergeDescendants to true.
This way, accessibility services can present the inner elements' content descriptions one after the other, in a single announcement. Consolidating related elements helps users of assistive technology discover the information on the screen more efficiently.
In the following snippet, the Row composable acts as the parent container.
Within the Row are related elements that show metadata for a blog
post—the author's avatar, the author's name, and the estimated reading time.
Setting mergeDescendants to true groups these inner elements, so
accessibility services can treat them as one unit.
@Composable private fun PostMetadata(metadata: Metadata) { // Merge elements below for accessibility purposes Row(modifier = Modifier.semantics(mergeDescendants = true) {}) { Image( imageVector = Icons.Filled.AccountCircle, contentDescription = null // decorative ) Column { Text(metadata.author.name) Text("${metadata.date} • ${metadata.readTimeMinutes} min read") } } }
When grouping related elements like in the previous example, make only the
parent container interactive. Avoid adding clickable or focusable modifiers
to the inner child elements. Instead, apply the modifiers to the parent Row
or Column.
Because accessibility services announce the inner elements' descriptions in a single utterance, it's important to keep each description as short as possible while still conveying the element's meaning.
Note: In general, when creating a content description for a group, avoid aggregating the text of its children. Doing so makes the group's description brittle, and when the text of a child changes, the group's description might no longer match the visible text.
In a list or a grid context, a screen reader might consolidate the text of a list or grid element's child text nodes. It is best to avoid modifying this announcement.
For more information about merging semantics, see Merging and clearing.
Headings within text
Some apps use headings to summarize groups of text that appear on screen. If
a particular element represents a heading, you can indicate its purpose
for accessibility services by setting the heading property in the semantics
modifier.
@Composable private fun Subsection(text: String) { Text( text = text, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.semantics { heading() } ) }
Users of accessibility services can choose to navigate between headings instead of between paragraphs or between words. This flexibility improves the text navigation experience.
For more information about the heading semantics property, see Headings.
Accessibility pane titles
In Android 9 (API level 28) and higher, you can provide accessibility-friendly titles for a screen's panes. For accessibility purposes, a pane is a visually distinct portion of a window.
For accessibility services to understand a pane's window-like behavior, give descriptive titles to your app's panes. Accessibility services can then provide more granular information to users when a pane's appearance or content changes.
ShareSheet( message = "Choose how to share this photo", modifier = Modifier .fillMaxWidth() .align(Alignment.TopCenter) .semantics { paneTitle = "New bottom sheet" } )
For more information about the paneTitle semantics property, see
Window-like components.
Decorative elements
If an element in your UI exists only for visual spacing or visual appearance purposes, set the appropriate properties on the element to indicate that accessibility services can ignore it.
For Image or Icon composables, set contentDescription = null. For other
purely decorative elements that provide no context or functionality, you can
use hideFromAccessibility. This semantics property tells accessibility
services to ignore the item.
If an interactive composable contains decorative, non-interactive child
elements, use clearAndSetSemantics to make sure that accessibility services
don't traverse them. Note that clearAndSetSemantics fully erases the default
semantics of an element and its children. This lets you define a new, unified
accessibility element. Normally, you use this approach for complex custom
components.
In the following example, Icon and Text are decorative child elements
inside a custom toggle. To prevent accessibility services from traversing
these children individually, you can clear their semantics by using
clearAndSetSemantics on the parent Row. This tells accessibility services
to treat the entire Row as a traversable toggle:
// Developer might intend this to be a toggleable. // Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied, // a custom description is set, and a Role is applied. @Composable fun FavoriteToggle() { val checked = remember { mutableStateOf(true) } Row( modifier = Modifier .toggleable( value = checked.value, onValueChange = { checked.value = it } ) .clearAndSetSemantics { stateDescription = if (checked.value) "Favorited" else "Not favorited" toggleableState = ToggleableState(checked.value) role = Role.Switch }, ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = null // not needed here ) Text("Favorite?") } }
For more information about clearing semantics, see Clear and set semantics.
Add accessibility actions
It's important to make sure that users of accessibility services have a way to complete all user flows in your app.
If your custom composable's interaction changes the app's state in a way that
isn't obvious, provide descriptive labels for standard tap actions using
parameters like onClickLabel or onLongClickLabel in Modifier.clickable
or Modifier.combinedClickable.
For complex interactions not mappable to standard taps, use customActions.
For example, if your app lets users drag an item to another location or swipe on an item in a list, you can provide an alternate way to complete these user flows by exposing the action to accessibility services. This way, users of TalkBack, Voice Access, or Switch Access can perform actions that might otherwise be available only through gestures.
In Compose, you can define custom accessibility actions through the
customActions property in the semantics modifier, using
CustomAccessibilityAction.
For example, if your app lets users swipe on an item to dismiss it, you can expose the functionality through a custom accessibility action:
SwipeToDismissBox( modifier = Modifier.semantics { // Represents the swipe to dismiss for accessibility customActions = listOf( CustomAccessibilityAction( label = "Remove article from list", action = { removeArticle() true } ) ) }, state = rememberSwipeToDismissBoxState(), backgroundContent = {} ) { ArticleListItem() }
With the custom accessibility action implemented, users can access the action through the actions menu.
For more information about custom actions, see Custom actions.
Make available actions understandable
When a UI element supports actions like touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more useful, specify a meaningful description for the action.
In Compose, standard interaction modifiers like clickable and
combinedClickable have built-in parameters (namely onClickLabel and
onLongClickLabel) that you can use to provide descriptions for the actions,
as in the following example:
var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) } val haptics = LocalHapticFeedback.current LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { items(photos, { it.id }) { photo -> ImageItem( photo, Modifier .combinedClickable( onClick = { activePhotoId = photo.id }, onLongClick = { haptics.performHapticFeedback(HapticFeedbackType.LongPress) contextMenuPhotoId = photo.id }, onLongClickLabel = stringResource(R.string.open_context_menu) ) ) } } if (contextMenuPhotoId != null) { PhotoActionsSheet( photo = photos.first { it.id == contextMenuPhotoId }, onDismissSheet = { contextMenuPhotoId = null } ) }
This results in TalkBack announcing "Open context menu," helping users understand the purpose of the action.
You can also specify a label directly in the semantics modifier.
For more information about responding to taps and clicks, see Tap and press and Interactive elements.
Use built-in accessibility features
When designing your app's UI, take advantage of built-in accessibility features to avoid reimplementing functionality that already exists. Material, Compose UI, and Foundation APIs implement and offer many accessible practices by default.
In Jetpack Compose, use built-in composables like Button, Switch, and
Checkbox to create accessible UIs. These components come prepackaged with
semantics modifiers, such as role and stateDescription, that you can use
to make your apps more accessible.
Apply semantics to custom components
When creating a custom component, be mindful of what kind of accessibility
support this component requires to fulfill its role. Often, the standard
Compose APIs that you're already using—such as clickable, toggleable, or
selectable—are sufficient because they automatically populate the semantics
tree for you.
However, some components require more specific information than standard
modifiers provide. In these cases, look for specialized modifiers (like
triStateToggleable) or, if none exist, explicitly provide semantics using
the low-level Modifier.semantics.
For example, consider a TriStateSwitch, a switch with three states (On,
Off, and Indeterminate).
While a standard toggleable modifier assumes two states, the
triStateToggleable modifier handles the complexity of the third state. It
automatically sets the accessibility Role (Switch) and State. This way,
accessibility services receive accurate information, and you don't need to
manually define the semantics.
The following code snippet shows a TriStateSwitch using this approach:
@Composable fun TriStateSwitch( state: ToggleableState, onClick: () -> Unit, modifier: Modifier = Modifier ) { // A real implementation would include custom drawing for the switch. // This example uses a Box to demonstrate the semantics. Box( modifier = modifier .size(width = 64.dp, height = 40.dp) // triStateToggleable handles the semantics (Role and State) // automatically, so explicit Modifier.semantics is not needed here. .triStateToggleable( state = state, onClick = onClick, role = Role.Switch ) // Add visual feedback based on the state .background( when (state) { ToggleableState.On -> Color.Green ToggleableState.Off -> Color.Gray ToggleableState.Indeterminate -> Color.Yellow } ) ) } // Usage within another composable: var state by remember { mutableStateOf(ToggleableState.Off) } TriStateSwitch( state = state, onClick = { state = when (state) { ToggleableState.Off -> ToggleableState.Indeterminate ToggleableState.Indeterminate -> ToggleableState.On ToggleableState.On -> ToggleableState.Off } } )
When building a custom component, make sure you provide all relevant semantic
properties for accessibility purposes. For example, if your component mimics
a standard control like a switch or button, these properties include the
component's role (such as Role.Switch or Role.Button), stateDescription
(such as "On", "Off", "Checked", or "Not checked"), and any relevant action
labels. For more information, see Custom components.
Use cues other than color
To assist users with color vision deficiencies, use cues other than color to distinguish UI elements within your app's screens. These techniques can include using different shapes or sizes, providing text or visual patterns, or adding audio- or touch-based (haptic) feedback to mark the elements' differences.
Figure 1 shows two versions of an activity. One version uses only color to distinguish between two possible actions in a workflow. The other version uses the best practice of including shapes and text in addition to color to highlight the differences between the two options:
Make media content more accessible
If you're developing an app that includes media content, such as a video clip or an audio recording, try to support users with different types of accessibility needs in understanding the material. In particular, try to do the following:
- Include controls that allow users to pause or stop the media, change the volume, and toggle subtitles (captions).
- If a video presents information that is vital to completing a workflow, provide the same content in an alternate format, such as a transcript.
Additional resources
For more information about making your app more accessible, see the following additional resources: