Más allá de TalkBack
La ceguera total es una fracción minúscula de la diversidad funcional que atraviesa tu base de usuarios. Esta lección cubre las cuatro dimensiones que quedan fuera del lector de pantalla y que afectan a muchísima más gente: el escalado de fuente no lineal introducido en Android 14 y por qué obliga a abandonar las alturas fijas, los umbrales de contraste de la norma y qué significan realmente, el mínimo de 48dp para objetivos táctiles y cómo ampliar el área sin tocar el diseño, y la navegación por foco con teclado, mando o `Switch Access`, que es la gran olvidada de las aplicaciones móviles.
Existe un sesgo persistente en cómo se enseña la accesibilidad móvil: se explica el lector de pantalla, se da por cubierto el tema y se pasa a otra cosa. El resultado son aplicaciones que un usuario ciego puede manejar y que una persona de sesenta años con presbicia, temblor esencial y el brillo al máximo bajo el sol no puede usar en absoluto. Los números son inequívocos: quienes activan un tamaño de letra mayor que el predeterminado se cuentan por cientos de millones, quienes tienen alguna forma de deficiencia en la percepción del color son alrededor de uno de cada doce hombres, y quienes fallan objetivos táctiles pequeños somos, en algún momento, prácticamente todos. Ninguna de esas personas usa TalkBack. Todas ellas se topan con tu interfaz tal cual la dibujaste, y lo que las excluye no es un árbol semántico incompleto sino una altura fija, un gris demasiado claro, un icono de veinticuatro puntos y un foco que no se ve. Esta lección trata de esas cuatro cosas.
- Diseñar composiciones que sobrevivan al escalado de fuente no lineal sin recortes ni solapamientos.
- Aplicar los umbrales de contraste correctos y entender por qué el color nunca puede ser el único portador de información.
- Garantizar objetivos táctiles de 48dp reales sin deformar la retícula visual.
- Hacer navegable la interfaz con foco físico y verificar el recorrido con teclado o mando.
Escalado de fuente: el fin de las alturas fijas
Android permite al usuario ampliar el tamaño del texto hasta el doscientos por ciento, y desde Android 14 lo hace con escalado no lineal: los tamaños pequeños crecen proporcionalmente más que los grandes, porque ampliar un titular de treinta puntos al doble lo vuelve inservible mientras que un pie de foto de doce sí lo necesita. La consecuencia inmediata es que la relación entre dos tamaños de texto no se conserva al escalar, y por tanto ningún cálculo de altura basado en el tamaño de fuente es fiable.
De aquí sale la regla más importante del apartado: usa sp para texto y dp para todo lo demás, y no derives alturas de contenedor del tamaño de fuente. Un Row con altura fija de 48dp que contiene un texto escalado al doscientos por ciento recorta el texto sin avisar. La solución es dejar que el contenido determine la altura y fijar solo un mínimo.
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 48.dp) // minimo, no altura fija
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(item.titulo, style = MaterialTheme.typography.bodyLarge)
}
El segundo hábito es evitar maxLines = 1 combinado con truncamiento en cualquier texto que porte información. Al escalar, ese truncamiento se come el contenido. Cuando el espacio es realmente escaso, la respuesta es reconfigurar la disposición —pasar de fila a columna— y no amputar el texto.
El tercero es tratar la escala como un dato de entrada más y no como una anomalía. Cuando una disposición no puede sobrevivir al escalado extremo por mucho que se flexibilice, la salida correcta es cambiar de disposición, y para eso el valor está disponible en la composición.
val escala = LocalDensity.current.fontScale
if (escala > 1.5f) {
Column { Etiqueta(); Valor() } // apilado cuando el texto es enorme
} else {
Row { Etiqueta(); Spacer(Modifier.weight(1f)); Valor() }
}
Y el cuarto, del que casi nadie se acuerda, es que los iconos que acompañan a un texto deben escalar con él. Un icono fijado en 24dp junto a un texto al doscientos por ciento se vuelve un punto insignificante y rompe la relación visual. Dimensionarlo a partir del tamaño de línea del estilo tipográfico mantiene la proporción sin recurrir a sp para una medida que no es texto.
En las opciones de desarrollador y en los ajustes de accesibilidad, sube el tamaño de fuente al máximo y el tamaño de pantalla a la densidad más alta, y recorre tus pantallas principales. Es la prueba de estrés más barata que existe y detecta más defectos reales que cualquier auditoría automática. Añade a tus previews una variante con fontScale = 2f para que el fallo aparezca en tiempo de diseño y no en producción.
Contraste y color como único canal
La norma establece una relación de contraste mínima de 4,5 a 1 entre texto y fondo para tamaños normales, y de 3 a 1 para texto grande —a partir de 18 puntos, o 14 en negrita—. Los elementos gráficos que portan información, como los bordes de un campo de formulario o la barra de un gráfico, exigen también 3 a 1. Esa relación se calcula sobre la luminancia relativa de ambos colores, no sobre su parecido perceptual, y por eso la intuición falla con frecuencia: dos colores muy distintos entre sí pueden tener luminancias casi idénticas y resultar ilegibles uno sobre otro.
flowchart LR A[Color de texto] --> C[Luminancia relativa] B[Color de fondo] --> C C --> D[Relacion de contraste] D --> E[Texto normal necesita 4 coma 5 a 1] D --> F[Texto grande necesita 3 a 1] D --> G[Graficos necesitan 3 a 1]
El error más frecuente no es el contraste insuficiente en el texto principal, que suele venir resuelto por el sistema de color de Material, sino en tres zonas periféricas: el texto secundario en gris claro, los estados deshabilitados y el texto sobre imágenes. El tercero es especialmente traicionero porque el contraste depende de la fotografía concreta, y la única solución robusta es interponer un velo de opacidad o un degradado.
Junto al contraste va una regla independiente y a menudo confundida con él: el color nunca puede ser el único portador de una distinción. Un campo de formulario que solo señala el error tiñendo su borde de rojo no comunica nada a quien no distingue el rojo del verde ni a quien mira la pantalla bajo luz solar directa. La distinción debe duplicarse en un segundo canal: un icono, un texto, un cambio de forma o de grosor.
Contraste
Es una propiedad medible del par de colores. Se verifica con una herramienta, se corrige en el sistema de diseño y se hereda en toda la aplicación.
Redundancia de canal
Es una propiedad del diseño de la información. No hay herramienta que la mida: requiere revisar cada distinción y preguntarse qué queda si el color desaparece.
Objetivos táctiles: 48dp no es negociable
La guía de la plataforma fija en 48dp por 48dp el tamaño mínimo de cualquier zona interactiva, lo que equivale aproximadamente a nueve milímetros físicos, que es el tamaño medio del área de contacto de un dedo. El error clásico consiste en confundir el tamaño del dibujo con el tamaño del objetivo: un icono de 24dp dentro de un contenedor sin relleno produce un objetivo de 24dp, la mitad de lo exigido.
Compose ofrece dos mecanismos. Los componentes de Material que aceptan interacción aplican automáticamente un área mínima aunque su contenido sea menor, comportamiento gobernado por LocalMinimumInteractiveComponentSize. Y para los casos propios existe Modifier.minimumInteractiveComponentSize, además de la opción explícita de dimensionar el contenedor.
Box(
modifier = Modifier
.size(48.dp) // objetivo tactil real
.clickable(onClick = onCerrar)
.padding(12.dp), // el icono queda a 24dp visuales
contentAlignment = Alignment.Center,
) {
Icon(Icons.Default.Close, contentDescription = "Cerrar")
}
Fíjate en el orden de los modificadores: el clickable se aplica sobre el tamaño completo y el relleno va después, de modo que el área sensible abarca los 48dp mientras el glifo se dibuja a su tamaño de diseño. Invertir ese orden reduce el objetivo al icono y anula el arreglo. Junto al tamaño importa la separación: dos objetivos correctos pero adyacentes producen pulsaciones erróneas, y conviene dejar al menos 8dp entre ellos.
Hay dos zonas donde este defecto reaparece con obstinación por mucho que se corrija en el resto de la aplicación. La primera son los enlaces incrustados dentro de un párrafo, cuya altura la determina la línea de texto y por tanto casi nunca llega al mínimo; la mitigación razonable es aumentar el interlineado y ofrecer la misma acción en un control de tamaño adecuado. La segunda son las listas densas con acciones secundarias al final de cada fila, donde la presión por caber empuja a reducir el icono hasta que el objetivo desaparece; ahí la solución suele ser mover esa acción a un menú de desbordamiento o a una acción personalizada.
Conviene también no confundir el mínimo con el óptimo. Los 48dp son el suelo por debajo del cual la tasa de error se dispara, no un objetivo de diseño: para acciones frecuentes o críticas, y para superficies grandes como tablets, ampliar el objetivo mejora la precisión de todo el mundo sin coste alguno.
Foco físico: teclado, mando y conmutadores
La navegación por foco es la dimensión más ignorada del desarrollo móvil, y sin embargo la usan tres poblaciones distintas: quien conecta un teclado a una tablet o a un plegable, quien maneja el dispositivo con Switch Access por limitación motora, y quien ejecuta la aplicación en Android TV, en un escritorio o en una ventana redimensionable.
Compose gestiona el foco con un sistema propio que infiere el orden de la geometría y admite intervención explícita. Los tres elementos que debes controlar son la focalizabilidad, el orden y la visibilidad del indicador.
val requester = remember { FocusRequester() }
Column(Modifier.focusGroup()) {
TextField(
value = texto,
onValueChange = { texto = it },
modifier = Modifier
.focusRequester(requester)
.onFocusChanged { estado -> resaltado = estado.isFocused },
)
}
El defecto más grave de esta dimensión no es el orden sino la invisibilidad: un elemento que recibe el foco sin ninguna señal visual deja al usuario completamente perdido, porque no hay forma de saber dónde está el cursor. Todo control focalizable necesita un indicador con contraste suficiente, y los componentes de Material lo traen, pero cualquier elemento pulsable construido a mano lo pierde salvo que lo dibujes.
El segundo defecto en frecuencia es la trampa de foco: un diálogo, una hoja inferior o un menú que no confina el recorrido dentro de sí mismo permite que el foco se escape al contenido de debajo, que sigue estando en el árbol aunque esté visualmente tapado. El usuario acaba interactuando con controles que no puede ver. La contención se declara marcando el contenedor modal como grupo de foco y retirando del árbol lo que quede detrás.
El tercero es el orden por geometría en disposiciones libres. Cuando los elementos no siguen una retícula limpia, la inferencia falla y hay que declarar el orden explícitamente con focusProperties, encadenando cada control con el siguiente.
Modifier.focusProperties {
next = siguienteCampo
previous = campoAnterior
}
Verificar esta dimensión es sorprendentemente barato: basta con emparejar un teclado por Bluetooth y recorrer un formulario completo con el tabulador. Si puedes rellenarlo y enviarlo sin tocar la pantalla y sin perder de vista dónde estás, la navegación por foco funciona.
Hay un desplazamiento conceptual que conviene hacer explícito, porque cambia la economía entera de este trabajo. El modelo mental por defecto trata la accesibilidad como una cuestión de minorías: hay un grupo pequeño de personas con discapacidad y se hace un esfuerzo adicional para incluirlas, lo cual es correcto éticamente y desastroso como argumento de ingeniería, porque cualquier gasto se evalúa contra un porcentaje de usuarios que siempre parece pequeño. El modelo más preciso, y el que la investigación en diseño inclusivo lleva décadas defendiendo, es que la capacidad funcional no es un atributo binario de las personas sino una variable continua que depende del contexto y del momento. Nadie tiene visión perfecta a las tres de la madrugada con la pantalla al mínimo. Nadie tiene motricidad fina caminando por la calle, con guantes, en un autobús que frena, con un niño en el otro brazo. Todo el mundo tiene una limitación auditiva en un bar ruidoso y una limitación cognitiva cuando está agotado o interrumpido. Las adaptaciones que llamamos de accesibilidad son, en realidad, adaptaciones para la degradación de las condiciones, y todos los usuarios atraviesan esa degradación a diario. Un objetivo táctil de 48dp no beneficia a quien tiene temblor esencial: beneficia a quien tiene temblor esencial y a todos los demás el veinte por ciento del tiempo. Un contraste de 4,5 a 1 no es para quien tiene baja visión: es para cualquiera con sol de frente. Cuando este marco sustituye al anterior, la pregunta deja de ser cuántos usuarios lo necesitan —una pregunta cuya respuesta siempre invita a posponer— y pasa a ser en cuántas situaciones falla mi interfaz. La respuesta a esa segunda pregunta nunca es pequeña, y es la única que consigue mover una hoja de ruta.
- Sube el tamaño de fuente al máximo y la densidad de pantalla al valor más alto, y recorre tus tres pantallas principales anotando cada recorte o solapamiento.
- Mide el contraste del texto secundario, de los estados deshabilitados y de cualquier texto sobre imagen. Corrige en el sistema de color, no en el punto de uso.
- Localiza una distinción que solo exista por color y duplícala en un segundo canal. Comprueba el resultado en escala de grises.
- Mide los objetivos táctiles de tu barra superior y de tus listas. Amplía los que queden por debajo de 48dp sin mover un solo píxel visible.
- Conecta un teclado o activa
Switch Accessy recorre un formulario completo sin tocar la pantalla. Anota en qué punto pierdes de vista el foco.