Lección 5 de 10 · 14 min de lectura · Revisado el 26/10/2026
Navegación, foco y estructura de pantallas: títulos, orden, diálogos y teclado externo
En las lecciones de iOS y Android viste cómo describir cada control: nombre, rol, estado y acciones. Pero una app no es un conjunto de controles sueltos, sino un recorrido entre pantallas, hojas, diálogos y menús. Para quien usa VoiceOver, TalkBack, Switch Control o un teclado externo, la pregunta constante es «¿dónde estoy y dónde está el foco?». Esta lección trata de esa capa: títulos de pantalla, orden de recorrido, encabezados, gestión del foco al navegar y al abrir o cerrar ventanas modales, y operación con teclado. Es, en la práctica, la fuente de muchos de los fallos más graves de una auditoría móvil, porque bloquean tareas completas.
Dos focos distintos: el de accesibilidad y el de entrada
En web solo hay un foco. En móvil conviven dos, y confundirlos es el origen de muchos errores:
- Foco de accesibilidad: el elemento que el lector de pantalla está leyendo, marcado con un recuadro (el «cursor de VoiceOver» o el rectángulo verde de TalkBack). Se mueve con los gestos de deslizar y puede situarse sobre cualquier elemento accesible, incluidos textos no interactivos.
- Foco de entrada: el elemento que recibe las pulsaciones de un teclado físico o de Full Keyboard Access, y el campo donde escribe el teclado en pantalla. Solo lo reciben los elementos enfocables (controles y campos).
En Android la diferencia es explícita: requestFocus() mueve el foco de entrada, mientras que el foco de accesibilidad se mueve con performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null). En Compose, un FocusRequester mueve el foco de entrada; si lo que necesitas es que TalkBack lea un elemento concreto, compruébalo siempre con TalkBack activo, porque no basta con que el campo tenga el cursor. En iOS, el foco de VoiceOver se dirige con las notificaciones .screenChanged y .layoutChanged (UIKit) o con @AccessibilityFocusState (SwiftUI), y el de teclado con el sistema de foco de UIKit (UIFocusSystem) o @FocusState en SwiftUI.
Punto clave
Al diseñar un cambio de pantalla o la aparición de un diálogo, decide explícitamente dónde debe quedar cada foco. Si no lo decides tú, lo decide el sistema, y a veces lo deja en un elemento que ya no existe o detrás de la ventana que acabas de abrir.
Títulos de pantalla y encabezados
El criterio 2.4.2 Página titulada no se traslada de forma literal: WCAG2ICT lo aplica al software en su conjunto y la EN 301 549 v3.2.1 (PDF, se abre en una ventana nueva) lo deja vacío en el capítulo 11. Aun así, que cada pantalla tenga un título que la identifique es la práctica esperada y la forma de saber dónde se está. No basta con que se vea: la tecnología de apoyo debe anunciarlo al llegar.
- iOS:
UINavigationControlleryNavigationStackanuncian el cambio y VoiceOver sitúa el foco al principio de la nueva pantalla. UsanavigationItem.titleo.navigationTitle("Mis citas")siempre, aunque la barra se oculte por diseño. En transiciones personalizadas, publicaUIAccessibility.post(notification: .screenChanged, argument: tituloLabel). - Android (Views): TalkBack anuncia el título de la actividad al cambiar de ventana. Con una sola actividad y varios fragmentos, actualiza el título o usa
ViewCompat.setAccessibilityPaneTitle(vista, "Mis citas")en el contenedor del fragmento para que el cambio se anuncie. - Compose: marca el contenedor de cada destino con
Modifier.semantics { paneTitle = "Mis citas" }y usa unTopAppBarcon título visible.
Dentro de la pantalla, los encabezados (.header en iOS, heading() en Compose, accessibilityHeading en Views) permiten saltar por secciones con el rotor de VoiceOver o los controles de lectura de TalkBack. Son el equivalente funcional de los skip links (enlaces para saltar bloques) de la web: en una pantalla de ficha con diez bloques, marcar cada título de sección como encabezado ahorra decenas de gestos.
Orden de recorrido
Los lectores recorren los elementos en un orden calculado a partir de la jerarquía y la posición en pantalla: de arriba abajo y, en cada fila, en el sentido de lectura. Ese orden falla con disposiciones en columnas, tarjetas con elementos superpuestos o botones flotantes. Los criterios afectados son 1.3.2 Secuencia significativa y 2.4.3 Orden del foco.
| Necesidad | iOS | Android |
|---|---|---|
| Definir el orden de los hijos | accessibilityElements (UIKit), .accessibilitySortPriority (SwiftUI) | accessibilityTraversalBefore/After (Views), traversalIndex (Compose) |
| Recorrer un bloque entero antes de salir | shouldGroupAccessibilityChildren, .accessibilityElement(children: .contain) | isTraversalGroup = true (Compose); en Views, un ViewGroup contenedor común, porque el recorrido sigue la jerarquía |
| Mover el foco tras un cambio | .layoutChanged con argumento, @AccessibilityFocusState | ACTION_ACCESSIBILITY_FOCUS, live region o paneTitle |
| Ocultar lo que hay detrás | accessibilityViewIsModal, .isModal | importantForAccessibility="noHideDescendants", Dialog |
Corrige el orden con estas API solo cuando la disposición no pueda reorganizarse: la primera opción es que el orden del código coincida con el orden visual lógico. Un caso típico es el botón flotante de acción («Nueva incidencia»): visualmente está abajo a la derecha, pero conceptualmente es una acción principal; colócalo en el recorrido justo después del título o del contenido que lo motiva, no al final de una lista de cien elementos.
Infografía · Proceso
Cómo gestionar un cambio de pantalla o un diálogo
Cinco pasos para que quien usa VoiceOver, TalkBack o teclado sepa siempre dónde está
- Paso 1: Anunciar el títuloLa pantalla o el diálogo tiene un título que el lector anuncia al llegar
- Paso 2: Colocar el focoEn el título o en el primer elemento lógico, nunca al final ni detrás
- Paso 3: Ocultar el fondoCon el diálogo abierto, lo que queda detrás no se puede recorrer
- Paso 4: Permitir cerrarBotón visible, gesto de escape de VoiceOver y botón Atrás de Android
- Paso 5: Devolver el focoAl cerrar, el foco vuelve al control que abrió el diálogo
Leer la infografía como texto
Proceso de cinco pasos en secuencia para gestionar un cambio de pantalla o la apertura de un diálogo en una app. Paso 1, anunciar el título: la nueva pantalla o el diálogo se identifica con un título que el lector de pantalla anuncia al llegar. Paso 2, colocar el foco: el foco va al título o al primer elemento lógico, nunca al final ni detrás. Paso 3, ocultar el fondo: mientras el diálogo está abierto, lo que queda detrás no se puede recorrer. Paso 4, permitir cerrar: con un botón visible, con el gesto de escape de VoiceOver y con el botón Atrás de Android. Paso 5, devolver el foco: al cerrar, el foco vuelve al control que abrió el diálogo o a otro lugar lógico.
Diálogos, hojas inferiores y menús
Una ventana modal accesible cumple cuatro condiciones: el foco entra en ella al abrirse, el contenido de detrás queda inaccesible mientras está abierta, se puede cerrar sin gestos especiales y, al cerrarse, el foco vuelve a un lugar lógico (normalmente el control que la abrió). Los componentes del sistema (UIAlertController, .sheet, .confirmationDialog, AlertDialog, ModalBottomSheet, DropdownMenu) cumplen casi todo de serie. Los problemas llegan con las capas personalizadas: una vista superpuesta dentro de la misma pantalla que, para el lector, no es modal.
// UIKit: capa personalizada que se comporta como modal
final class FiltroOverlay: UIView {
override func didMoveToSuperview() {
super.didMoveToSuperview()
accessibilityViewIsModal = true // ignora lo que hay detrás
UIAccessibility.post(notification: .screenChanged, argument: tituloLabel)
}
// Gesto de escape de VoiceOver (dos dedos en «Z»)
override func accessibilityPerformEscape() -> Bool {
cerrar(); return true
}
}
// SwiftUI: capa propia
PanelFiltros()
.accessibilityAddTraits(.isModal)
.accessibilityAction(.escape) { mostrarFiltros = false } En Android, si la capa es una vista dentro de la misma ventana, marca el contenido de fondo con importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS mientras esté abierta y restáuralo al cerrar; gestiona el botón Atrás del sistema (OnBackPressedCallback o BackHandler en Compose) para cerrarla. En Compose, la vía segura es usar Dialog o ModalBottomSheet, que crean su propia ventana; si construyes una capa a mano, aplica Modifier.clearAndSetSemantics { } al contenido de fondo mientras está abierta.
Error frecuente
Una hoja inferior personalizada con los filtros de búsqueda se abre sobre la lista, pero TalkBack sigue recorriendo la lista que queda detrás: el usuario oye resultados en vez de filtros y no sabe que hay una hoja abierta. Al cerrarla con «Aplicar», el foco se pierde y vuelve al principio de la pantalla. Incumple 2.4.3 Orden del foco y, según el caso, 1.3.2 y 4.1.2. La corrección: componente modal del sistema o capa con fondo oculto, título anunciado al abrir y foco devuelto al botón «Filtros» al cerrar.
Teclado externo y foco visible
Muchas personas con discapacidad motriz usan el móvil o la tableta con un teclado Bluetooth o con un pulsador que lo emula. En iOS, Full Keyboard Access (Ajustes > Accesibilidad > Teclados) permite moverse con Tab y las flechas y activar con la barra espaciadora; en Android, Tab y las flechas mueven el foco de entrada y Intro activa. Se aplican los mismos criterios que en web:
- 2.1.1 Teclado y 2.1.2 Sin trampas para el foco del teclado: todo control es alcanzable y se puede salir de cualquier componente (un mapa, un reproductor, un campo de texto multilínea).
- 2.4.7 Foco visible: los componentes estándar dibujan el indicador; en controles propios hay que pintarlo (estado
focuseden Views,onFocusChangedoModifier.focusablecon indicación en Compose,isFocusedenUIFocusEnvironment). - 2.4.11 Foco no oculto (mínimo): el elemento con foco no queda tapado por completo por barras fijas, banners o el propio teclado en pantalla. Desplaza el contenido para que el campo activo quede a la vista.
En Compose, Modifier.focusProperties { next = siguiente } y los grupos de foco permiten ajustar el recorrido de teclado cuando el orden por defecto no sirve; en Views, nextFocusDown, nextFocusForward y similares.
Pruébalo tú
Elige una app que uses a diario (tu banco, el transporte de tu ciudad). Activa VoiceOver o TalkBack y abre un menú, un filtro o un diálogo de confirmación. Comprueba tres cosas: ¿se anuncia el título al abrirse?, ¿puedes llegar con gestos de deslizar a lo que hay detrás?, ¿dónde queda el foco al cerrarlo? Anota el resultado; lo usarás en el caso práctico.
Resumen
- En móvil hay dos focos, el de accesibilidad y el de entrada; decide dónde queda cada uno en cada cambio de pantalla y comprueba el resultado con el lector real.
- Cada pantalla tiene un título que se anuncia al llegar (práctica ligada a 2.4.2):
navigationTitle, título de actividad,paneTitle; los encabezados permiten saltar por secciones. - El orden de recorrido sigue la lógica de la tarea (1.3.2, 2.4.3); corrígelo con las API de orden solo si no puedes reorganizar la disposición.
- Una capa modal recibe el foco, oculta el fondo, se cierra con el gesto de escape o el botón Atrás y devuelve el foco al control que la abrió.
- Con teclado externo se aplican 2.1.1, 2.1.2, 2.4.7 y 2.4.11: todo alcanzable, sin trampas y con un foco visible que nada tapa.
Comprueba lo que has aprendido
Esta lección se puede leer libremente. Para hacer su test de 3 preguntas, los ejercicios prácticos con páginas reales, guardar tu avance y optar al examen y al certificado, matricúlate en el curso.
Matricularme · nivel 6 (119 €)