Accesibilidad en Compose

Las apps escritas en Compose deben admitir soluciones de accesibilidad para usuarios con diferentes necesidades. Los servicios de accesibilidad permiten transformar lo que se muestra en pantalla a un formato más adecuado para un usuario que tiene una necesidad específica. Para brindar compatibilidad con los servicios de accesibilidad, las apps usan API en el framework de Android para exponer información semántica sobre los elementos de su IU. El framework de Android notificará a los servicios de accesibilidad sobre esa información semántica. Cada servicio de accesibilidad puede elegir la mejor forma de describir la app al usuario. Android ofrece varios servicios de accesibilidad, como TalkBack y Accesibilidad con interruptores.

Semántica

Compose usa propiedades de semántica para pasar información a los servicios de accesibilidad. Las propiedades semánticas proporcionan información sobre los elementos de la IU que se muestran al usuario. La mayoría de los elementos integrados componibles como Text y Button completan estas propiedades semánticas con información inferida del elemento componible y sus objetos secundarios. Algunos modificadores, como toggleable y clickable, también establecerán propiedades semánticas. Sin embargo, en algunas ocasiones, el framework necesita más información para comprender cómo describir un elemento de la IU al usuario.

En este documento, se describen varias situaciones en las que necesitas agregar información adicional de manera explícita a un elemento componible para que se pueda describir correctamente al framework de Android. También se explica cómo reemplazar por completo la información semántica para un elemento componible determinado. Se supone que tienes conocimientos básicos sobre la accesibilidad en Android.

Casos de uso comunes

Para ayudar a que los usuarios con necesidades de accesibilidad utilizan tu app correctamente, debes seguir las prácticas recomendadas que se describen en esta página.

Considera el tamaño mínimo del objetivo táctil

Todos los elementos de la pantalla en los que se puede hacer clic, que se pueden tocar o con los que se puede interactuar deben ser lo suficientemente grandes para permitir una interacción confiable. Cuando establezcas el tamaño de esos elementos, asegúrate de configurar su tamaño mínimo en 48 dp para seguir los Lineamientos de Accesibilidad de Material Design.

Existen componentes en Material, como Checkbox, RadioButton, Switch, Slider y Surface, que establecen este tamaño mínimo de manera interna, pero solo el usuario puede modificar el componente. Por ejemplo, si Checkbox tiene su parámetro onCheckedChange establecido en un valor no nulo, incluirá un padding para tener un ancho y un alto de 48 dp como mínimo.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Cuando el parámetro onCheckedChange se establece en nulo, el padding no está incluido, ya que no se puede interactuar directamente con el componente.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Cuando implementas controles de selección como Switch, RadioButton o Checkbox, por lo general, quitas el comportamiento de hacer clic en un contenedor superior, configuras la devolución de llamada de clics en el elemento componible a null y agregas un modificador toggleable o selectable al elemento superior que admite composición.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

Cuando el tamaño de un elemento componible para hacer clic es menor que el tamaño del objetivo táctil mínimo, Compose aumenta el tamaño del objetivo táctil. Para ello, expande el tamaño del objetivo fuera de los límites del elemento componible.

En el siguiente ejemplo, creamos un objeto Box muy pequeño para hacer clic. El área del objetivo táctil se expande automáticamente más allá de los límites de Box, por lo que, cuando se presione junto a Box, se activará el evento de clic.

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

A fin de evitar una posible superposición entre áreas táctiles de diferentes elementos componibles, siempre debes intentar usar un tamaño mínimo lo suficientemente grande para el elemento componible. En nuestro ejemplo, que se muestra a continuación, implicaría el uso del modificador sizeIn para establecer el tamaño mínimo de la casilla interna:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

Agrega etiquetas de clic

Puedes usar una etiqueta de clic para agregar significado semántico al comportamiento de los clics de un elemento componible. Estas etiquetas de clic describen lo que sucede cuando el usuario interactúa con el elemento componible. Los servicios de accesibilidad usan etiquetas de clic para describirles la app a los usuarios con necesidades específicas.

Para configurar la etiqueta de clics, pasa un parámetro en el modificador clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

Como alternativa, si no tienes acceso al modificador en el que se puede hacer clic, puedes configurar la etiqueta de clics en el modificador semantics:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

Describe elementos visuales

Cuando defines una Image o un Icon componible, no existe una manera automática de que el framework de Android comprenda lo que se muestra. Debes pasar una descripción textual del elemento visual.

Imagina una pantalla en la que el usuario pueda compartir la página actual con amigos. Esta pantalla contiene un ícono para compartir en el que se puede hacer clic:

Una barra de íconos en los que se puede hacer clic, con el ícono

Solamente en función del ícono, el framework de Android no puede determinar cómo describirlo a un usuario con discapacidad visual. El framework de Android necesita una descripción textual adicional del elemento.

El parámetro contentDescription se usa para describir un elemento visual. Debes usar una string localizada, ya que esto se comunicará al usuario.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Algunos elementos visuales son puramente decorativos, y es posible que no quieras comunicárselos al usuario. Cuando configuras el parámetro contentDescription como null, le indicas al framework de Android que ese elemento no tiene acciones ni estado asociados.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

Depende de ti decidir si un elemento visual específico necesita una contentDescription. Pregúntate si el elemento transmite información que el usuario necesitará para realizar su tarea. De lo contrario, es mejor no incluir una descripción.

Combina elementos

Los servicios de accesibilidad como TalkBack y Accesibilidad con interruptores permiten que los usuarios muevan el enfoque entre los elementos de la pantalla. Es importante que los elementos estén enfocados en el nivel de detalle correcto. Si todos los elementos componibles de bajo nivel están centrados de forma independiente, el usuario tendrá que interactuar mucho para moverse por toda la pantalla. Si los elementos se combinan de manera demasiado agresiva, es posible que los usuarios no comprendan qué elementos pertenecen a ellos.

Cuando aplicas un modificador clickable en un elemento componible, Compose fusiona automáticamente todos los elementos que contiene. Esto también se aplica a ListItem; los elementos dentro de un elemento de lista se fusionarán, y los servicios de accesibilidad los verán como un elemento.

Se puede formar un grupo lógico con un conjunto de elementos componibles, pero ese grupo no admitirá clics ni será parte de un elemento de lista. Querrás que los servicios de accesibilidad los vean como un solo elemento. Por ejemplo, imagina un elemento componible que muestra el avatar de un usuario, su nombre y algún dato adicional:

Grupo de elementos de la IU que incluye el nombre de un usuario. Se selecciona el nombre.

Puedes indicarle a Compose que combine esos elementos mediante el parámetro mergeDescendants en el modificador semantics. De esta manera, los servicios de accesibilidad seleccionarán solo el elemento combinado, y se combinarán todas las propiedades semánticas de los elementos subordinados.

@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")
        }
    }
}

Los servicios de accesibilidad ahora se enfocarán en todo el contenedor de una vez, lo que combinará su contenido:

Grupo de elementos de la IU que incluye el nombre de un usuario. Se seleccionan todos los elementos.

Agrega acciones personalizadas

Observa el siguiente elemento de la lista:

Un elemento de lista común que contiene un título, un autor y un ícono de favorito.

Cuando usas un lector de pantalla como TalkBack para escuchar lo que se muestra, primero se selecciona todo el elemento y, luego, el ícono de favorito.

El elemento de lista, con todos los elementos seleccionados juntos.

El elemento de lista, que tiene solo el ícono de favorito seleccionado

En una lista larga, esto puede ser muy repetitivo. Un mejor enfoque sería definir una acción personalizada que le permita a un usuario agregar el elemento a favoritos. Recuerda que también deberás quitar explícitamente el comportamiento del ícono de favoritos para asegurarte de que no lo seleccione el servicio de accesibilidad. Para ello, usa el modificador clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

Describe el estado de un elemento

Un elemento componible puede definir una stateDescription para la semántica que usa el framework de Android a fin de leer el estado en el que está el elemento. Por ejemplo, un elemento componible que se puede activar o desactivar puede aparecer como "Checked" ("Verificado") o "Unchecked" ("No verificado"). En algunos casos, puede que quieras anular las etiquetas de descripción de estado predeterminadas que usa Compose. Puedes hacerlo especificando explícitamente las etiquetas de descripción de estado antes de definir un elemento componible como un elemento que se puede activar o desactivar:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

Define encabezados

En algunas ocasiones, las apps muestran mucho contenido en una pantalla, en un contenedor desplazable. Por ejemplo, una pantalla puede mostrar el contenido completo de un artículo que el usuario está leyendo:

Captura de pantalla de una entrada de blog, con el texto del artículo en un contenedor desplazable.

Los usuarios con necesidades de accesibilidad tendrán dificultades para navegar por esta pantalla. Para facilitar la navegación, puedes indicar qué elementos son encabezados. En el ejemplo anterior, cada título de subsección se puede definir como un encabezado para los fines de accesibilidad. Algunos servicios de accesibilidad, como TalkBack, permiten que los usuarios naveguen directamente de un encabezado a otro.

En Compose, para indicar que un elemento componible es un encabezado, define su propiedad semántica:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Pruebas automatizadas de las propiedades de accesibilidad

Si personalizas las propiedades semánticas de tu app, por ejemplo, si sigues los casos de uso mencionados anteriormente, puedes verificar la precisión y evitar regresiones mediante pruebas automatizadas de la IU.

Por ejemplo, para probar que la etiqueta de clic de un elemento esté configurada correctamente, usa el siguiente código:

@Test
fun test() {
    composeTestRule
        .onNode(nodeMatcher)
        .assert(
            SemanticsMatcher("onClickLabel is set correctly") {
                it.config.getOrNull(SemanticsActions.OnClick)?.label == "My Click Label"
            }
        )
}

Cómo crear elementos componibles personalizados de bajo nivel

Un caso de uso más avanzado implica reemplazar ciertos componentes de Material de tu app por versiones personalizadas. En estos casos, es fundamental que tengas en cuenta las consideraciones de accesibilidad. Supongamos que reemplazas el objeto Checkbox de Material con tu propia implementación. Sería muy fácil olvidar agregar el modificador triStateToggleable, que administra las propiedades de accesibilidad de este componente.

Como regla general, debes tener en cuenta la implementación del componente en la biblioteca de Material e imitar cualquier comportamiento de accesibilidad que encuentres. Además, usa activamente los modificadores de Foundation, en lugar de los modificadores del nivel de IU, ya que incluyen consideraciones de accesibilidad de forma inmediata. Asegúrate de probar la implementación de tus componentes personalizados con varios servicios de accesibilidad para verificar su comportamiento.

Modifica el orden de recorrido con isTraversalGroup y traversalIndex

De forma predeterminada, el comportamiento del lector de pantalla de accesibilidad en una app de Compose se implementa en el orden de lectura esperado, que suele ser de izquierda a derecha y, luego, de arriba a abajo. Sin embargo, hay algunos tipos de diseños de apps en los que el algoritmo no puede determinar el orden de lectura real sin sugerencias adicionales. En las apps basadas en View, puedes solucionar estos problemas mediante las propiedades traversalBefore y traversalAfter. A partir de Compose 1.5, Compose proporciona una API igual de flexible, pero con un modelo conceptual nuevo.

isTraversalGroup y traversalIndex son propiedades semánticas que te permiten controlar la accesibilidad y el orden del enfoque de TalkBack en situaciones en las que el algoritmo de ordenamiento predeterminado no es apropiado. isTraversalGroup identifica grupos con importancia semántica, mientras que traversalIndex ajusta el orden de los elementos individuales dentro de esos grupos. Puedes usar isTraversalGroup solo o con traversalIndex para una mayor personalización.

En esta página, se describe cómo usar isTraversalGroup y traversalIndex en tu app para controlar el orden de recorrido del lector de pantalla.

Agrupa elementos con isTraversalGroup

isTraversalGroup es una propiedad booleana que define si un nodo de semántica es un grupo de recorrido. Este tipo de nodo es aquel cuya función es servir como límite o borde en la organización de sus elementos secundarios.

Si configuras isTraversalGroup = true en un nodo, se visitarán todos los elementos secundarios de ese nodo antes de moverse a otros elementos. Puedes configurar isTraversalGroup en nodos enfocables que no sean del lector de pantalla, como Columnas, Filas o Cuadros.

En este ejemplo, se modifica un fragmento para usar isTraversalGroup. El siguiente fragmento emite cuatro elementos de texto. Los dos elementos de la izquierda pertenecen a un elemento CardBox, mientras que los dos de la derecha pertenecen a otro elemento CardBox:

// CardBox() function takes in top and bottom sample text.
@Composable
fun CardBox(
    topSampleText: String,
    bottomSampleText: String,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        Column {
            Text(topSampleText)
            Text(bottomSampleText)
        }
    }
}

@Composable
fun TraversalGroupDemo() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is "
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
            topSampleText1,
            bottomSampleText1
        )
        CardBox(
            topSampleText2,
            bottomSampleText2
        )
    }
}

El código produce un resultado similar al siguiente:

Diseño con dos columnas de texto, en la que la columna de la izquierda dice "Esta oración está en la columna izquierda" y la columna de la derecha dice "Esta oración está a la derecha".
Figura 1: Un diseño con dos oraciones (una en la columna izquierda y otra en la columna derecha).

Debido a que no se estableció ninguna semántica, el comportamiento predeterminado del lector de pantalla es desviar los elementos de izquierda a derecha y de arriba a abajo. Debido a este valor predeterminado, TalkBack lee los fragmentos de oraciones en el orden incorrecto:

"Esta oración se encuentra en" → "Esta oración es" → "la columna izquierda". → "a la derecha."

Para ordenar los fragmentos correctamente, modifica el fragmento original para establecer isTraversalGroup en true:

@Composable
fun TraversalGroupDemo2() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is"
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
//      1,
            topSampleText1,
            bottomSampleText1,
            Modifier.semantics { isTraversalGroup = true }
        )
        CardBox(
//      2,
            topSampleText2,
            bottomSampleText2,
            Modifier.semantics { isTraversalGroup = true }
        )
    }
}

Debido a que isTraversalGroup se establece específicamente en cada CardBox, los límites de CardBox se respetan cuando se ordenan sus elementos. En este caso, el CardBox izquierdo se lee primero, seguido del CardBox derecho.

Ahora TalkBack lee los fragmentos de las oraciones en el orden correcto:

"Esta oración está en" → "la columna izquierda". → "Esta oración es" → "a la derecha".

Personaliza aún más el orden de recorrido con traversalIndex

traversalIndex es una propiedad flotante que te permite personalizar el orden de recorrido de TalkBack. Si no basta con agrupar los elementos para que TalkBack funcione correctamente, puedes usar traversalIndex junto con isTraversalGroup para personalizar aún más el orden del lector de pantalla.

La propiedad traversalIndex tiene las siguientes características:

  • Los elementos con valores de traversalIndex más bajos tienen prioridad.
  • Puede ser positivo o negativo.
  • El valor predeterminado es 0f.
  • Solo afecta a los nodos enfocables del lector de pantalla, como los elementos en pantalla, como Texto o Botones. Por ejemplo, configurar solo traversalIndex en una columna no tendría ningún efecto, a menos que la columna también tenga un valor de isTraversalGroup configurado.

En el siguiente ejemplo, se muestra cómo puedes usar traversalIndex y isTraversalGroup juntos.

Ejemplo: formato de reloj transversal

Una cara de reloj es una situación común en la que el ordenamiento de recorrido estándar no funciona. El ejemplo de esta sección se basa en un selector de hora, en el que un usuario puede recorrer los números de una cara de reloj y seleccionar dígitos para las franjas horarias y los minutos.

Una cara de reloj con un selector de hora sobre ella.
Figura 2: La imagen de una cara de reloj

En el siguiente fragmento simplificado, hay un CircularLayout en el que se dibujan 12 números, que comienzan con 12 y se mueven en el sentido de las manecillas del reloj alrededor del círculo:

@Composable
fun ClockFaceDemo() {
    CircularLayout {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier) {
        Text((if (value == 0) 12 else value).toString())
    }
}

Debido a que la cara de reloj no se lee de forma lógica con el orden predeterminado de izquierda a derecha y de arriba a abajo, TalkBack lee los números desordenados. Para rectificar esto, usa el valor del contador incremental, como se muestra en el siguiente fragmento:

@Composable
fun ClockFaceDemo() {
    CircularLayout(Modifier.semantics { isTraversalGroup = true }) {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) {
        Text((if (value == 0) 12 else value).toString())
    }
}

Para establecer correctamente el orden de recorrido, primero haz que CircularLayout sea un grupo de recorrido y configura isTraversalGroup = true. Luego, a medida que se dibuja el texto de cada reloj en el diseño, establece su traversalIndex correspondiente en el valor del contador.

Debido a que el valor del contador aumenta de forma continua, el traversalIndex de cada valor de reloj es mayor a medida que se agregan números a la pantalla: el valor del reloj 0 tiene un traversalIndex de 0, el valor del reloj 1 tiene un traversalIndex de 1, y así sucesivamente. De esta forma, se establece el orden en el que TalkBack los leerá. Ahora, los números dentro de CircularLayout se leen en el orden esperado.

Debido a que los traversalIndexes que se establecieron solo son relativos a otros índices dentro de la misma agrupación, se conservó el resto del orden de la pantalla. En otras palabras, los cambios semánticos que se muestran en el fragmento de código anterior solo modifican el orden dentro de la cara de reloj que tiene isTraversalGroup = true configurado.

Ten en cuenta que, sin establecer la semántica de CircularLayout's en isTraversalGroup = true, aún se aplican los cambios de traversalIndex. Sin embargo, sin el CircularLayout para vincularlos, los doce dígitos de la cara de reloj se leen en último lugar, después de visitar todos los demás elementos de la pantalla. Esto ocurre porque todos los demás elementos tienen un traversalIndex predeterminado de 0f, y los elementos de texto del reloj se leen después de todos los demás elementos 0f.

Ejemplo: Personaliza el orden de recorrido para el botón de acción flotante

En este ejemplo, se usa traversalIndex y isTraversalGroup para controlar el orden de recorrido de un botón de acción flotante (BAF) de Material Design. Este ejemplo se basa en el siguiente diseño:

Diseño con una barra superior de la app, texto de ejemplo, un botón de acción flotante y una barra inferior de la app
Figura 3: Diseño con una barra superior de la app, texto de ejemplo, un botón de acción flotante y una barra inferior de la app

De forma predeterminada, el diseño anterior tiene el siguiente orden de TalkBack:

Barra superior de la app → Textos de ejemplo del 0 al 6 → Botón de acción flotante (BAF) → Barra de la app inferior

Es posible que desees que el lector de pantalla se enfoque primero en el BAF. Para establecer un traversalIndex en un elemento de Material, como un BAF, haz lo siguiente:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

En este fragmento, crear un cuadro con isTraversalGroup establecido en true y configurar un traversalIndex en el mismo cuadro (-1f es menor que el valor predeterminado de 0f) significa que el cuadro flotante se coloca antes de todos los demás elementos en pantalla.

A continuación, puedes colocar el cuadro flotante y otros elementos en un andamiaje, que implementa un diseño simple de Material Design:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

TalkBack interactúa con los elementos en el siguiente orden:

BAF → Barra superior de la app → Textos de ejemplo del 0 al 6 → Barra inferior de la app

Más información

Para obtener más información sobre la compatibilidad de accesibilidad en tu código de Compose, haz el codelab de accesibilidad en Jetpack Compose.