A thumb pressing a tactile accessibility switch with a scarlet-red rubber dome on a Scandinavian oak surface, headphones blurred beside — the visual marker for mobile-native accessibility-API comparison.
Image description: A thumb pressing a tactile accessibility switch with a scarlet-red rubber dome on a Scandinavian oak surface, headphones blurred beside — the visual marker for mobile-native accessibility-API comparison.

Guía de ingeniería · API de a11y móvil

API de accesibilidad nativas para móviles en 2026: UIAccessibility, AccessibilityNode y la web

Un análisis comparativo de UIAccessibility en iOS, AccessibilityNodeInfo en Android y los puentes multiplataforma que intentan reconciliarlos: qué mapea con precisión, qué no y cómo encaja la web móvil.

API de accesibilidad nativas para móviles en 2026:
UIAccessibility, AccessibilityNode y la web

iOS y Android exponen un árbol de accesibilidad con todas las funciones al lector de pantalla de la plataforma, y los dos árboles no coinciden en nada más allá de los conceptos básicos de etiqueta y rol. Hemos mapeado todos los primitivos que VoiceOver y TalkBack consumen realmente en 2026, la forma en que React Native, Flutter y Kotlin Multiplatform intentan tender un puente entre ellos, y el punto donde la accesibilidad de WebView móvil se desmorona silenciosamente.

2
API nativas comparadas
3
puentes multiplataforma
28
primitivos mapeados
13 min de lectura
Actualizado en mayo de 2026

1. UIAccessibility de iOS — etiquetas, rasgos, sugerencias, valores

Todo elemento visible en una pantalla de iOS tiene, o puede tener, una representación de accesibilidad. Apple incluye esa representación a través del protocolo informal UIAccessibility, implementado por UIView y todos los controles del sistema, y a través de UIAccessibilityElement, una clase ligera que se asigna para las partes de la interfaz que se dibujan pero que no son vistas en sí mismas: caracteres en un gráfico dibujado a medida, glifos dentro de una escena de Core Graphics, regiones dentro de un CALayer. VoiceOver, Switch Control, Acceso completo con teclado y Control por voz consumen el mismo protocolo; aprenderlo una vez da acceso a cuatro tecnologías de apoyo.

El protocolo expone cuatro primitivos relevantes en casi cualquier pantalla. La etiqueta de accesibilidad es el nombre breve y legible del elemento: «Enviar», «Foto de perfil de Asha», «Atrás». Los rasgos de accesibilidad son una máscara de bits de indicadores similares a roles — .button, .header, .image, .selected, .adjustable, .staticText, .updatesFrequently — que indican a VoiceOver cómo comportarse con el elemento y qué gestos activar. El valor de accesibilidad es una representación de cadena del estado actual («Activado», «75 %», «Jueves, 22 de mayo»). La sugerencia de accesibilidad es la explicación más extensa y opcional («Pulsa dos veces para abrir el visor de fotos») que VoiceOver pronuncia tras una pausa si el usuario no actúa solo con la etiqueta.

Los cuatro primitivos se combinan. Un interruptor se anuncia como etiqueta + rasgo + valor: «Wi-Fi, botón interruptor, Activado». Un control deslizante se anuncia como etiqueta + rasgo + valor + sugerencia: «Volumen, ajustable, 60 por ciento, desliza hacia arriba o abajo para ajustar». Una barra de un gráfico dibujada a medida se representa como una cadena de UIAccessibilityElement, cada uno con una etiqueta, un valor y un marco dentro de su contenedor. La cadena es la superficie de la API: VoiceOver la recorre linealmente cuando el usuario desliza a la derecha y respeta el orden en que los elementos se publican a través del array accessibilityElements del contenedor.

SwiftUI usa el mismo protocolo, con una interfaz más amigable

Los modificadores de vista .accessibilityLabel(), .accessibilityValue(), .accessibilityHint() y .accessibilityAddTraits() de SwiftUI se compilan en las mismas propiedades de UIAccessibility sobre el UIView subyacente. SwiftUI también añade .accessibilityElement(children:), que resuelve el problema de los «caracteres en un gráfico» de forma más declarativa que el enfoque UIKit, aunque el contrato en tiempo de ejecución que ve VoiceOver es idéntico. Aprender los nombres de UIKit sigue mereciendo la pena, porque todos los ejemplos de Apple, todas las respuestas en Stack Overflow y todas las auditorías de accesibilidad los utilizan.


2. AccessibilityNodeInfo de Android — roles, acciones, importantForAccessibility

Android sigue un camino diferente. Donde iOS incorpora la accesibilidad como un protocolo plano en cada vista, Android serializa el árbol de accesibilidad completo como un grafo de objetos AccessibilityNodeInfo, siendo cada uno una instantánea de una vista en el momento en que llega una consulta de TalkBack. El sistema construye las instantáneas de forma perezosa; un View publica su nodo sobreescribiendo onInitializeAccessibilityNodeInfo() (o, en Compose, estableciendo modificadores de semántica), y la plataforma ensambla las relaciones padre-hijo en un árbol que refleja la jerarquía de vistas.

Los primitivos difieren de los de iOS en tres aspectos significativos. Primero, Android expone un rol mediante un campo className de tipo cadena: android.widget.Button, android.widget.CheckBox, android.widget.EditText. TalkBack lee el nombre de la clase y decide cómo anunciarlo («botón», «casilla de verificación», «cuadro de edición»). Compose traduce sus semánticas Role.Button, Role.Checkbox, Role.RadioButton al mismo campo. El rol es más granular que una máscara de bits de rasgo de iOS, pero también más rígido: no existe un rol «totalmente personalizado» a menos que se acepte el anuncio como «vista».

Segundo, Android representa la interactividad como un conjunto de acciones vinculadas al nodo: ACTION_CLICK, ACTION_LONG_CLICK, ACTION_SCROLL_FORWARD, ACTION_SET_TEXT, ACTION_FOCUS, y una larga lista de acciones personalizadas que se pueden registrar con AccessibilityNodeInfo.AccessibilityAction. TalkBack muestra las acciones personalizadas a través del «rotor de acciones»: el usuario desliza hacia arriba con un dedo y escucha cada acción personalizada por su nombre. iOS tiene el mismo concepto (UIAccessibilityCustomAction), pero en Android la lista de acciones es la superficie; en iOS lo es el vocabulario de gestos.

Tercero, Android dispone de importantForAccessibility, un enum por vista (auto, yes, no, noHideDescendants) que controla si el nodo aparece en el árbol en absoluto. noHideDescendants es la herramienta más poderosa en accesibilidad Android y la que más frecuentemente se olvida: elimina todo el subárbol del recorrido de TalkBack, el equivalente de aria-hidden=“true” en la web. iOS no tiene un equivalente exacto; lo más cercano es establecer accessibilityElements en un array vacío en el contenedor, lo que solo elimina los hijos directos del contenedor, no todo el subárbol.

La discordancia de «live region»

Android expone ViewCompat.setAccessibilityLiveRegion() con tres valores: none, polite, assertive. El vocabulario refleja el de ARIA, casi. TalkBack respeta los niveles de cortesía de forma fiable. iOS no tiene nada comparable a nivel de protocolo: los cambios se anuncian llamando a UIAccessibility.post(notification: .announcement, argument: “Guardado”), un disparo imperativo puntual que no se vincula a una vista. Los puentes multiplataforma tienen que simular uno de estos mecanismos sobre el otro, y la discordancia de impedancia aflora en todos los marcos analizados en la sección 3.


3. Puentes multiplataforma — React Native, Flutter, Kotlin Multiplatform

Todo marco de desarrollo móvil multiplataforma debe tomar las dos API anteriores y presentar una superficie única con la forma del marco. Ninguno lo logra por completo. Los tres enfoques dominan el mercado en 2026 — React Native, Flutter y Kotlin Multiplatform con Compose Multiplatform — y cada uno representa un compromiso ligeramente distinto entre abstracción y filtración de detalles nativos.

React Native 0.76
Puente JS a UIKit nativo y Android View
El mapeo más explícito, y el que más se filtra
Puente iOSaccessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState en Pressable y View se mapean casi 1:1 a UIAccessibility, aunque los nombres de los roles son el vocabulario de React Native, no el de iOS.
Puente AndroidLos mismos props JS se mapean a AccessibilityNodeInfo a través de un adaptador del lado de Yoga; accessibilityRole=“button” establece className en android.widget.Button.
InconvenienteEl prop accessibilityLiveRegion es solo para Android: en iOS no hace nada silenciosamente, y es necesario llamar a AccessibilityInfo.announceForAccessibility() manualmente.
Flutter 3.27
Renderizado personalizado · árbol de a11y sintético
El más uniforme, y el más opaco
EnfoqueFlutter renderiza todo en un lienzo Skia, por lo que construye su propio árbol SemanticsNode y lo serializa a la plataforma bajo demanda.
Ruta iOSLos SemanticsNodes se traducen en instancias de UIAccessibilityElement sobre la vista Flutter, con rasgos mapeados desde los conjuntos SemanticsAction y SemanticsFlag.
Ruta AndroidEl mismo árbol SemanticsNode se serializa en nodos AccessibilityNodeInfo por la vista Android de Flutter; las acciones se convierten en AccessibilityActions; la live region se convierte en SemanticsFlag.isLiveRegion.
Kotlin Multiplatform · Compose Multiplatform
Runtime compartido de Compose · a11y por plataforma
El más reciente, con las costuras más visibles por plataforma
EnfoqueModifier.semantics { } de Compose define roles y acciones una vez; cada plataforma destino traduce el mismo bloque de semánticas a su propia API de a11y nativa.
Plataforma iOSEl runtime de Compose para iOS recorre el árbol de semánticas y construye UIAccessibilityElements, aunque la implementación de iOS es más joven que la de Android y aún le faltan varios tipos semánticos.
Plataforma AndroidLa ruta más madura: las semánticas se convierten en AccessibilityNodeInfo a través de la misma capa compose-ui-semantics que usa Compose nativo de Android.

El patrón es el mismo en los tres casos: un árbol semántico sintético con la forma del marco en un lado, dos árboles de accesibilidad con la forma de la plataforma en el otro, y un traductor intermedio que resuelve bien los casos simples y los complejos con una pérdida notable de fidelidad. Los casos simples — un botón con etiqueta, una imagen con texto alternativo, un encabezado — se traducen sin pérdida. Los casos complejos — un gesto personalizado con dos dedos, un gráfico cuyos elementos deberían ser un grupo enfocable, una live region que debe activarse en iOS sin un ajuste de cortesía vinculado a la vista — filtran el vocabulario de la plataforma subyacente hacia el código multiplataforma, o simplemente no se traducen.

«El primer 80 por ciento de la accesibilidad móvil es idéntico en todos los marcos. El 20 por ciento restante es donde cada marco revela en qué API nativa piensa en realidad.»

— Mesa de ingeniería de Disability World, mayo de 2026

4. La brecha de WebView — cuándo la accesibilidad web móvil falla silenciosamente

Tanto iOS como Android renderizan contenido web a través de un WebView del sistema: WKWebView en iOS y android.webkit.WebView (o Chrome Custom Tabs) en Android. En ambos casos, el WebView es una caja negra desde la perspectiva de la aplicación host: la aplicación ve una única vista, pero el lector de pantalla ve el árbol de accesibilidad DOM completo en su interior. El puente entre los dos árboles es el punto donde una cantidad sorprendente de accesibilidad móvil falla silenciosamente.

El mecanismo es, en apariencia, sencillo. Cuando el foco del lector de pantalla entra en un WebView, la plataforma lee el árbol de accesibilidad del documento directamente desde el motor del navegador — WebKit en iOS, Blink en Android — y lo recorre como un subárbol del árbol de la aplicación host. Los roles, las etiquetas y los atributos ARIA de la web se traducen al vocabulario de la plataforma en tiempo real. Un elemento button sin rol explícito dentro del WebView se anuncia como botón en ambas plataformas; una región aria-live=“polite” se anuncia correctamente en ambas; un aria-label en un enlace aparece como la etiqueta de accesibilidad del enlace. Durante los tres primeros años de la web móvil, esto simplemente funcionaba.

El fallo aparece en tres puntos. Primero, los gestos personalizados definidos en la aplicación host — un deslizamiento con dos dedos para cerrar, un magic tap para reproducir y pausar — son invisibles para el contenido del WebView; se activan en el objetivo incorrecto o no se activan en absoluto cuando el foco está dentro del documento. Segundo, los UIAccessibilityElement de la aplicación host dibujados sobre el WebView (un botón de acción flotante, una barra de herramientas personalizada) compiten con el árbol del WebView por el orden de recorrido, y el orden de lectura resultante es no determinista entre versiones de iOS. Tercero — y este es el mayor modo de fallo individual en la accesibilidad web móvil — el WebView en iOS no respeta los niveles de cortesía de aria-live de la misma manera que Safari en una pestaña: el mecanismo de anuncios de WKWebView elimina la distinción entre polite y assertive, por lo que toda actualización en vivo se trata como polite independientemente del marcado.

Dos vistas del mismo DOM
En una pestaña de Mobile Safari
<div role="alert" aria-live="assertive">
  Connection lost — retrying.
</div>

VoiceOver en una pestaña normal de Safari interrumpe la locución actual y pronuncia el mensaje de inmediato. La cortesía assertive se respeta de principio a fin a través de WebKit.

El mismo DOM dentro de un WKWebView
<div role="alert" aria-live="assertive">
  Connection lost — retrying.
</div>

Mismo marcado, mismo motor de navegador, pero el puente de accesibilidad del WKWebView hacia UIKit degrada el anuncio a un mensaje polite diferido. El usuario lo escucha con retraso, a veces después de haber escrito ya en el formulario que acaba de fallar.

La solución multiplataforma que realmente funciona

Para anuncios dentro de un WebView, el único patrón multiplataforma fiable en 2026 consiste en exponer un puente JavaScript hacia la aplicación host — un pequeño manejador postMessage — y enrutar los anuncios assertive fuera del DOM, hacia la aplicación host, y de vuelta a través de UIAccessibility.post(notification: .announcement, …) en iOS o announceForAccessibility() en Android. El aria-live de la web solo sobrevive para mensajes genuinamente polite donde una latencia de unos segundos es aceptable.


5. La tabla de correspondencias — qué equivale a qué

Hemos mapeado 28 primitivos que VoiceOver y TalkBack consumen realmente en la práctica: la unión de la superficie del protocolo UIAccessibility de iOS, la superficie AccessibilityNodeInfo de Android y los props multiplataforma más utilizados de React Native y Flutter. La tabla siguiente captura solo las filas conflictivas: los primitivos cuyo mapeo es incompleto, asimétrico o sorprendente. Las filas con mapeo limpio (etiqueta, rol de botón, rol de imagen, encabezado) se han omitido por extensión.

CapacidadUIAccessibility de iOSAccessibilityNodeInfo de AndroidReact Native 0.76Flutter 3.27
Texto de sugerencia (explicación más larga)accessibilityHinttooltipText (API 28+)accessibilityHint (solo iOS)SemanticsProperties.hint
Cortesía de live regionN/A — solo post imperativosetAccessibilityLiveRegion()accessibilityLiveRegion (solo Android)SemanticsFlag.isLiveRegion
Ocultar subárbol de a11yaccessibilityElementsHidden (solo hijos)importantForAccessibility=“noHideDescendants”accessibilityElementsHidden / importantForAccessibilityWidget ExcludeSemantics
Acción personalizada (rotor / menú)UIAccessibilityCustomActionAccessibilityNodeInfo.AccessibilityActionaccessibilityActions + onAccessibilityActionSemanticsAction con etiqueta personalizada
Semánticas de ajustable / control deslizanteUIAccessibilityTraitAdjustable + accessibilityIncrementRangeInfo + ACTION_SCROLL_FORWARDaccessibilityRole=“adjustable” + manejadoresSlider expone SemanticsAction.increase
Nivel de encabezadoUIAccessibilityTraitHeader (sin nivel)setHeading(true) (sin nivel)accessibilityRole=“header” (sin nivel)SemanticsProperties.headingLevel (1–6)
Estado seleccionado / activadoUIAccessibilityTraitSelectedsetSelected(true) + setCheckable()accessibilityState={selected, checked}SemanticsFlag.isSelected
Semánticas de grupo / contenedorshouldGroupAccessibilityChildrensetScreenReaderFocusable(true)accessible={true} en el padreWidget MergeSemantics
Anunciar un mensaje puntualUIAccessibility.post(.announcement, …)view.announceForAccessibility()AccessibilityInfo.announceForAccessibility()SemanticsService.announce()

Tres patrones destacan en la tabla. Primero, la asimetría en torno a las live regions es la mayor fuente de divergencia multiplataforma: Android tiene un ajuste de cortesía por vista, iOS solo tiene un post imperativo global, y todos los marcos anteriores se ven obligados a disimular la diferencia. Segundo, los niveles de encabezado son el único punto en que Flutter mejora genuinamente a ambas plataformas nativas; los primitivos de iOS y Android solo saben «esto es un encabezado», no «esto es un H3 bajo un H2». Tercero, el primitivo «ocultar de la accesibilidad» es más flexible en Android que en iOS: noHideDescendants oculta un subárbol completo en un solo movimiento, mientras que iOS requiere ocultar individualmente los hijos de cada contenedor.


6. El manual de referencia para móviles nativos

1

Aprender el vocabulario nativo antes que el del marco

Todo puente multiplataforma — React Native, Flutter, Compose Multiplatform — tiene su propio nombrado para los props de accesibilidad, y todos esos nombres son una ligera imprecisión sobre lo que la plataforma subyacente hace realmente. Cuando un lector de pantalla no anuncia correctamente, el error casi siempre reside en la API nativa a la que se tradujo el marco, no en el prop del marco que se estableció. Conviene leer la documentación de UIAccessibility y la de AccessibilityNodeInfo al menos una vez; la documentación del marco solo tiene sentido después.

2

Probar los anuncios en vivo específicamente en iOS

La asimetría de live region de la sección 2 implica que cualquier código que asuma que aria-live=“assertive” o accessibilityLiveRegion=“assertive” funciona va a degradarse silenciosamente en iOS. Se recomienda construir un pequeño arnés de prueba que active tanto un anuncio polite como uno assertive en ambas plataformas, con VoiceOver y TalkBack en dispositivos reales, antes de publicar cualquier función cuya experiencia de usuario dependa de que el usuario escuche un cambio de estado.

3

Enrutar fuera de WebViews todo lo que sea assertive

La degradación de anuncios assertive por parte de WKWebView no es un error que Apple vaya a corregir pronto: ha sido igual en todas las versiones de iOS desde la 14. Si se distribuye una aplicación híbrida donde el usuario puede encontrarse con un error crítico dentro de un WebView, debe enrutarse el anuncio a través de un puente JS hacia el host y dejar que el host active el anuncio de la plataforma. La web sola no es suficiente.

4

Usar la semántica de «merge» o «group» del marco, no elemento por elemento

Tanto iOS (shouldGroupAccessibilityChildren), Android (setScreenReaderFocusable) como Flutter (MergeSemantics) ofrecen un mecanismo para colapsar un clúster visual — un icono más una etiqueta más un valor — en un único elemento de accesibilidad. Debe usarse. El comportamiento predeterminado de «cada hoja es un elemento enfocable» convierte un chip de navegación de seis elementos en seis deslizamientos de VoiceOver.

5

Auditar con Accessibility Inspector y los ajustes de desarrollador de TalkBack

Ambas plataformas incluyen un inspector oficial y gratuito para el árbol de accesibilidad en vivo: Accessibility Inspector en macOS (emparejado con el simulador o dispositivo iOS conectado) y la superposición «Mostrar foco de accesibilidad» más «Ajustes de desarrollador» en Android. Deben usarse para leer el árbol de la propia aplicación tal como lo ve el lector de pantalla; no debe asumirse que el registro de depuración del marco muestra lo mismo que la plataforma muestra a TalkBack.


Conclusión: el marco está aguas abajo de la plataforma

Es tentador creer — y la documentación de los marcos alienta esta creencia — que una API de accesibilidad multiplataforma es una abstracción única y unificada sobre dos API nativas equivalentes. La tabla de correspondencias de la sección 5 refuta esa unificación. Las dos API nativas fueron diseñadas de forma independiente, por dos equipos distintos, en torno a dos modelos mentales diferentes de cómo el lector de pantalla debe recorrer un documento; las diferencias son reales, se filtran a través de todos los marcos, y la filtración aparece en las partes de la experiencia de usuario que más importan: actualizaciones en vivo, gestos personalizados, subárboles ocultos, jerarquías de encabezados.

La buena noticia, tras ese párrafo: lo básico funciona. Un botón con etiqueta, una imagen con texto alternativo, un encabezado al inicio de una sección — todo eso se traduce a través de todos los marcos y se anuncia correctamente en ambas plataformas. Si solo se usan esos primitivos, no es necesario pensar en UIAccessibility ni en AccessibilityNodeInfo; los valores predeterminados del marco son honestos. El problema comienza cuando la interfaz de usuario empieza a hacer algo interesante, que es también cuando la accesibilidad empieza a importar más.

El manual de la sección 6 es la versión más breve del argumento que lleva la experiencia funcional al mayor número de usuarios con discapacidad: pensar primero en primitivos nativos, probar en dispositivos reales en ambas plataformas, enrutar fuera de WebViews cuando se necesita, agrupar nodos hoja deliberadamente y usar los inspectores oficiales. El marco elegido ayuda con el primer 80 por ciento y se aparta del camino para el 20 por ciento restante. Ese 20 por ciento restante es donde vive el usuario de lector de pantalla.

«VoiceOver y TalkBack leen dos documentos diferentes a partir del mismo código fuente. Que el usuario note la diferencia es una medida de hasta qué punto se ha comprendido la plataforma que subyace al marco.»

— Mesa de ingeniería de Disability World, mayo de 2026