Saltar al contenido
Todas las entradas

Accesibilidad en Android en 2026: una checklist de TalkBack y semántica de Compose que se sostiene

Una checklist práctica de accesibilidad en Android para 2026 — TalkBack, semántica de Compose, áreas táctiles y la pasada de pruebas que hago en cada pantalla antes de publicar.

MFKAPPS 5 min de lectura

La accesibilidad en Android suele tratarse como un punto de la checklist previa al lanzamiento, encajado después de que la UI está “terminada”. Ese orden está invertido, y se nota: una pantalla construida sin pensar en la semántica tarda horas en corregirse después, mientras que la misma pantalla construida con semántica desde el principio casi no cuesta nada extra. Esta es la checklist que realmente aplico — no un documento de políticas, una lista de cosas que hago mientras escribo la UI en Compose, más la pasada de pruebas antes de publicar cualquier cosa.

Por qué corregir después sale caro y construir desde el principio no

TalkBack, el lector de pantalla de Android, no lee píxeles — lee el árbol semántico que Compose genera junto con tu UI. Si nunca piensas en ese árbol mientras escribes una pantalla, TalkBack termina leyendo detalles crudos de implementación: un botón solo con ícono se anuncia como “Botón”, una imagen decorativa se anuncia como “Imagen”, y una fila de tres composables separados se anuncia como tres paradas separadas en vez de una sola frase. Corregir eso después significa volver a cada composable preguntando “qué debería decir esto en realidad”, lo cual es más lento que hacer la misma pregunta la primera vez.

La solución es tratar la semántica como parte de la API del componente, no como una ocurrencia tardía pegada al final de la cadena de Modifier.

La checklist

1. Cada control no textual tiene un contentDescription — o ninguno

Si un botón tiene texto visible, no agregues una descripción redundante — TalkBack ya lee el texto, y una descripción duplicada hace que se lea dos veces. Si un botón es solo un ícono, la descripción es obligatoria:

IconButton(onClick = { onDelete(item.id) }) {
    Icon(
        imageVector = Icons.Default.Delete,
        contentDescription = stringResource(R.string.delete_item, item.name)
    )
}

Las imágenes puramente decorativas reciben explícitamente contentDescription = null. Una imagen sin ninguna descripción igual se anuncia como “imagen sin etiqueta”, lo cual es peor que el silencio.

2. Agrupa contenido relacionado con mergeDescendants

Una tarjeta con un título, un subtítulo y una insignia se lee por defecto como tres paradas separadas de TalkBack, lo que significa tres deslizamientos para escuchar una sola fila. Envuelve el grupo para que se anuncie como una sola unidad:

Row(
    modifier = Modifier.semantics(mergeDescendants = true) {}
) {
    Text(title)
    Text(subtitle)
    Badge { Text(status) }
}

Prueba esto activando TalkBack y deslizando por la pantalla una vez. Si una sola fila lógica requiere más de un deslizamiento, necesita fusionarse.

3. Las áreas táctiles son de 48dp, no el tamaño visual del ícono

Un ícono de 24dp sin relleno es un área táctil de 24dp, lo cual falla tanto las pautas de accesibilidad como la usabilidad básica para cualquiera con destreza reducida. Modifier.minimumInteractiveComponentSize() resuelve esto en el Compose actual sin cálculos manuales de relleno — aplícalo a cada ícono tocable, no solo a los que se sienten apretados en una revisión.

4. No comuniques el estado solo con color

Un borde rojo en un campo inválido es invisible para un usuario daltónico y no se anuncia para un usuario de TalkBack. Empareja cada señal que dependa solo del color con un cambio de texto o ícono: un mensaje de error bajo el campo, un ícono de error junto a la etiqueta. Esto también es simplemente mejor UX para un usuario vidente que mira rápido — el color solo requiere más atención para interpretarse que una palabra.

5. Regiones activas para contenido que cambia sin un clic

Si un total se actualiza después de que el usuario escribe un monto, TalkBack no lo notará a menos que el composable esté marcado como región activa:

Text(
    text = "Total: $formattedTotal",
    modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite }
)

Polite espera a que termine el anuncio actual; Assertive interrumpe. Usa Assertive con moderación — para un error que necesita atención inmediata, no para un total que va cambiando.

La pasada de pruebas

La revisión de código probablemente capta solo la mitad de lo que realmente importa, porque la mayoría de estos problemas solo son obvios con el lector de pantalla activado. Antes de publicar una pantalla, hago tres verificaciones:

  1. TalkBack, deslizando por toda la pantalla con la pantalla apagada, solo escuchando. Si no puedo saber qué hace un control solo por el anuncio, necesita una mejor descripción.
  2. Accessibility Scanner (la app independiente de Google) para el tamaño de las áreas táctiles y la relación de contraste — señala ambas automáticamente y apunta al composable exacto.
  3. Escala de fuente al 200% en la configuración del sistema. Un texto que se trunca, se superpone o se corta a esa escala es un error, no un caso extremo — una parte significativa de los usuarios reales usa escalas de fuente aumentadas de forma permanente.

Esto importa más para algunas apps que para otras. OldSchool, la app de recordatorios de medicación, se dirige a una base de usuarios mayores donde escalas de fuente más grandes y lectores de pantalla no son casos extremos en absoluto — están más cerca de la mediana. Haber construido la checklist en la biblioteca de componentes una sola vez me evitó tener que rederivarla pantalla por pantalla a medida que la app crecía.

La conclusión

El trabajo de accesibilidad hecho al final de un proyecto es caro porque es correctivo — alguien tiene que notar el vacío y luego volver a corregir cada pantalla individualmente. El trabajo de accesibilidad hecho como parte de escribir cada composable es casi gratis, porque las preguntas (“cómo se anuncia esto”, “es una parada o tres”, “el área táctil es real”) se responden en segundos mientras ya estás mirando el código. Trata la checklist como un hábito para pantallas nuevas, no como un simulacro de incendio de la semana de lanzamiento, y deja de ser una checklist del todo.