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.

Engineering primer · API a11y mobile

Les API d'accessibilité mobile native en 2026 : UIAccessibility, AccessibilityNode et le web

Comparaison détaillée entre iOS UIAccessibility, Android AccessibilityNodeInfo et les bridges multiplateforme qui tentent de les réconcilier — ce qui se mappe proprement, ce qui ne le fait pas, et la place du web mobile.

Les API d’accessibilité mobile native en 2026 :
UIAccessibility, AccessibilityNode et le web

iOS et Android exposent chacun un arbre d’accessibilité complet au lecteur d’écran de la plateforme — et ces deux arbres ne s’accordent sur rien au-delà des bases label-et-rôle. Nous avons cartographié chaque primitif que VoiceOver et TalkBack consomment réellement en 2026, la façon dont React Native, Flutter et Kotlin Multiplatform tentent de les réconcilier, et l’endroit où l’accessibilité du WebView mobile chute silencieusement dans le vide.

2
API natives comparées
3
bridges multiplateformes
28
primitifs cartographiés
13 min de lecture
Mis à jour en mai 2026

1. iOS UIAccessibility — labels, traits, hints, valeurs

Tout élément visible sur un écran iOS possède, ou peut posséder, une représentation d’accessibilité. Apple la fournit via le protocole informel UIAccessibility, implémenté par UIView et chaque contrôle système, ainsi que via UIAccessibilityElement, une classe légère que l’on alloue pour les parties de l’interface dessinées mais qui ne sont pas elles-mêmes des vues — des caractères dans un graphique personnalisé, des glyphes dans une scène Core Graphics, des régions à l’intérieur d’un CALayer. VoiceOver, Switch Control, Full Keyboard Access et Voice Control consomment tous le même protocole ; l’apprendre une fois couvre quatre technologies d’assistance.

Le protocole expose quatre primitifs essentiels pour presque tous les écrans. Le label d’accessibilité est le nom court et lisible de l’élément — « Envoyer », « Photo de profil d’Asha », « Retour ». Les traits d’accessibilité sont un masque de bits de drapeaux similaires à des rôles — .button, .header, .image, .selected, .adjustable, .staticText, .updatesFrequently — qui indiquent à VoiceOver comment se comporter autour de l’élément et quels gestes activer. La valeur d’accessibilité est une représentation textuelle de l’état actuel (« Activé », « 75 % », « Jeudi 22 mai »). Le hint d’accessibilité est l’explication plus longue et facultative (« Double-touchez pour ouvrir le visualiseur de photos ») que VoiceOver prononce après un délai si l’utilisateur n’agit pas sur le seul label.

Les quatre primitifs se combinent. Un commutateur s’annonce label + trait + valeur : « Wi-Fi, bouton commutateur, Activé ». Un curseur s’annonce label + trait + valeur + hint : « Volume, ajustable, 60 pour cent, balayez vers le haut ou le bas pour régler ». Une barre de graphique dessinée personnalisée s’exprime comme une chaîne de UIAccessibilityElements, chacun avec un label, une valeur et un cadre dans son conteneur. La chaîne est la surface de l’API — VoiceOver la parcourt linéairement quand l’utilisateur balaye vers la droite, et respecte l’ordre dans lequel les éléments sont publiés via le tableau accessibilityElements du conteneur.

SwiftUI utilise le même protocole, avec une façade plus conviviale

Les modificateurs de vue .accessibilityLabel(), .accessibilityValue(), .accessibilityHint() et .accessibilityAddTraits() en SwiftUI compilent vers les mêmes propriétés UIAccessibility sur la UIView sous-jacente. SwiftUI ajoute aussi .accessibilityElement(children:), qui résout le problème des « caractères dans un graphique » de manière plus déclarative que l’approche UIKit — mais le contrat d’exécution que VoiceOver voit est identique. Apprendre les noms UIKit reste utile, car tous les exemples Apple, toutes les réponses Stack Overflow et tous les audits d’accessibilité les utilisent.


2. Android AccessibilityNodeInfo — rôles, actions, importantForAccessibility

Android emprunte une voie différente. Là où iOS accroche l’accessibilité à un protocole plat sur chaque vue, Android sérialise l’intégralité de l’arbre d’accessibilité sous forme d’un graphe d’objets AccessibilityNodeInfo, chacun étant un instantané d’une vue au moment où une requête TalkBack arrive. Le framework construit les instantanés de manière paresseuse ; une View publie son nœud en surchargeant onInitializeAccessibilityNodeInfo() (ou, dans Compose, en définissant des modificateurs semantics), et la plateforme tisse les relations parent-enfant en un arbre qui reflète la hiérarchie de vues.

Les primitifs diffèrent d’iOS de trois façons significatives. Premièrement, Android expose un rôle via un champ className de type chaîne — android.widget.Button, android.widget.CheckBox, android.widget.EditText. TalkBack lit le nom de classe et décide comment annoncer (« bouton », « case à cocher », « zone de texte »). Compose traduit ses semantics Role.Button, Role.Checkbox, Role.RadioButton dans le même champ. Le rôle est plus précis qu’un masque de bits de traits iOS, mais aussi plus rigide — il n’existe pas de rôle « entièrement personnalisé » sans accepter l’annonce « vue ».

Deuxièmement, Android représente l’interactivité comme un ensemble d’actions attachées au nœud : ACTION_CLICK, ACTION_LONG_CLICK, ACTION_SCROLL_FORWARD, ACTION_SET_TEXT, ACTION_FOCUS, et une longue liste d’actions personnalisées que l’on peut enregistrer avec AccessibilityNodeInfo.AccessibilityAction. TalkBack expose les actions personnalisées via le rotor « actions » — l’utilisateur balaye vers le haut avec un doigt et entend chaque action personnalisée par son nom. iOS dispose du même concept (UIAccessibilityCustomAction), mais sur Android la liste d’actions est la surface ; sur iOS c’est le vocabulaire des gestes.

Troisièmement, Android possède importantForAccessibility, une énumération par vue (auto, yes, no, noHideDescendants) qui contrôle si le nœud apparaît dans l’arbre. noHideDescendants est l’outil le plus puissant de l’accessibilité Android et celui que l’on oublie le plus souvent — il supprime l’intégralité du sous-arbre de la traversée TalkBack, l’équivalent de aria-hidden=“true” sur le web. iOS n’a pas d’équivalent exact ; le plus proche consiste à définir accessibilityElements sur un tableau vide dans le conteneur, ce qui ne supprime que les enfants directs du conteneur, pas tout le sous-arbre.

La discordance des « live regions »

Android expose ViewCompat.setAccessibilityLiveRegion() avec trois valeurs : none, polite, assertive. Le vocabulaire reflète ARIA — presque. TalkBack respecte les niveaux de politesse de manière fiable. iOS n’a rien de comparable au niveau du protocole : les mises à jour s’annoncent en appelant UIAccessibility.post(notification: .announcement, argument: “Enregistré”), un appel impératif unique qui ne s’attache pas à une vue. Les bridges multiplateformes doivent simuler l’un de ces mécanismes par-dessus l’autre, et l’inadéquation d’impédance se manifeste dans chaque framework examiné à la section 3.


3. Bridges multiplateformes — React Native, Flutter, Kotlin Multiplatform

Chaque framework mobile multiplateforme doit prendre les deux API ci-dessus et présenter une surface unique aux formes du framework. Aucun d’entre eux ne réussit entièrement. Les trois approches dominent le marché en 2026 — React Native, Flutter et Kotlin Multiplatform avec Compose Multiplatform — chacune représentant un compromis légèrement différent entre fuite et abstraction.

React Native 0.76
Bridge JS vers UIKit natif et Android View
Le mapping le plus explicite — et le plus poreux
Bridge iOSaccessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState sur Pressable et View se mappent presque 1:1 vers UIAccessibility — mais les noms de rôles sont le vocabulaire React Native, pas celui d’iOS.
Bridge AndroidLes mêmes props JS se mappent vers AccessibilityNodeInfo via un adapteur côté Yoga ; accessibilityRole=“button” définit className sur android.widget.Button.
Point de vigilanceLa prop accessibilityLiveRegion est uniquement Android — sur iOS elle ne fait silencieusement rien, et il faut appeler AccessibilityInfo.announceForAccessibility() manuellement.
Flutter 3.27
Rendu personnalisé · arbre a11y synthétique
Le plus uniforme — et le plus opaque
ApprocheFlutter rend tout sur un canvas Skia, il construit donc son propre arbre SemanticsNode et le sérialise vers la plateforme à la demande.
Chemin iOSLes SemanticsNodes sont traduits en instances UIAccessibilityElement sur la vue Flutter, avec des traits mappés depuis les ensembles SemanticsAction et SemanticsFlag.
Chemin AndroidLe même arbre SemanticsNode est sérialisé en nœuds AccessibilityNodeInfo par la vue Android de Flutter ; les actions deviennent AccessibilityActions ; la live region devient SemanticsFlag.isLiveRegion.
Kotlin Multiplatform · Compose Multiplatform
Runtime Compose partagé · a11y par cible
Le plus récent, avec les coutures les plus visibles par plateforme
ApprocheLe Modifier.semantics { } de Compose définit les rôles et les actions une seule fois ; chaque cible traduit le même bloc semantics vers sa propre API a11y native.
Cible iOSLe runtime Compose-for-iOS parcourt l’arbre semantics et construit des UIAccessibilityElements — mais l’implémentation iOS est plus jeune que celle d’Android et manque encore de plusieurs types sémantiques.
Cible AndroidLe chemin mature : les semantics deviennent AccessibilityNodeInfo via la même couche compose-ui-semantics qu’utilise Compose natif Android.

Le schéma est le même pour tous les trois : un arbre sémantique synthétique aux formes du framework d’un côté, deux arbres d’accessibilité aux formes de la plateforme de l’autre, et un traducteur entre les deux qui gère bien les cas simples et les cas complexes avec une perte de fidélité notable. Les cas simples — un bouton avec un label, une image avec un texte alternatif, un titre — se transmettent sans perte. Les cas complexes — un geste personnalisé à deux doigts, un graphique dont les éléments devraient former un groupe focalisable, une live region qui doit se déclencher sur iOS sans paramètre de politesse lié à une vue — font remonter le vocabulaire de la plateforme sous-jacente dans le code multiplateforme, ou échouent simplement à se traduire.

« Les 80 premiers pour cent de l’accessibilité mobile sont identiques dans tous les frameworks. Les 20 derniers pour cent révèlent quelle API native chaque framework pense secrètement. »

— Bureau engineering de Disability World, mai 2026

4. La faille WebView — quand l’accessibilité mobile-web échoue silencieusement

iOS et Android affichent tous deux le contenu web via un WebView système — WKWebView sur iOS, android.webkit.WebView (ou Chrome Custom Tabs) sur Android. Dans chaque cas, le WebView est une boîte noire du point de vue de l’application hôte : l’application voit une seule vue, mais le lecteur d’écran voit l’intégralité de l’arbre d’accessibilité DOM à l’intérieur. Le pont entre les deux arbres est l’endroit où une proportion surprenante de l’accessibilité mobile échoue silencieusement.

Le mécanisme est, en apparence, simple. Lorsque le focus d’un lecteur d’écran entre dans un WebView, la plateforme lit l’arbre d’accessibilité du document directement depuis le moteur du navigateur — WebKit sur iOS, Blink sur Android — et le parcourt comme un sous-arbre de l’arbre de l’application hôte. Les rôles, labels et attributs ARIA du web sont traduits en temps réel dans le vocabulaire de la plateforme. Un élément button sans rôle explicite dans le WebView s’annonce comme un bouton sur les deux plateformes ; une région aria-live=“polite” s’annonce correctement sur les deux ; un aria-label sur un lien apparaît comme le label d’accessibilité du lien. Durant les trois premières années de la vie du web mobile, cela fonctionnait simplement.

La falaise apparaît en trois endroits. Premièrement, les gestes personnalisés définis dans l’application hôte — un balayage à deux doigts pour fermer, un magic-tap pour lire et mettre en pause — sont invisibles pour le contenu du WebView ; ils se déclenchent sur la mauvaise cible ou ne se déclenchent pas du tout lorsque le focus est dans le document. Deuxièmement, les UIAccessibilityElements de l’application hôte dessinés par-dessus le WebView (un bouton d’action flottant, une barre d’outils personnalisée) entrent en concurrence avec l’arbre du WebView pour l’ordre de traversée, et l’ordre de lecture qui en résulte est non déterministe selon les versions iOS. Troisièmement — et c’est le mode d’échec le plus important de l’accessibilité mobile-web — le WebView sur iOS ne respecte pas les niveaux de politesse aria-live comme Safari le fait dans un onglet : le plomberie d’annonces de WKWebView abandonne la distinction polite/assertive, de sorte que chaque mise à jour live est traitée comme polite quel que soit le balisage.

Deux vues du même DOM
Dans un onglet Mobile Safari
<div role="alert" aria-live="assertive">
  Connection lost — retrying.
</div>

VoiceOver dans un onglet Safari normal interrompt l’énoncé en cours et prononce le message immédiatement. La politesse assertive est respectée de bout en bout via WebKit.

Le même DOM dans un WKWebView
<div role="alert" aria-live="assertive">
  Connection lost — retrying.
</div>

Même balisage, même moteur de navigateur — mais le bridge d’accessibilité WKWebView vers UIKit rétrograde l’annonce en message polite différé. L’utilisateur l’entend après un délai, parfois après avoir déjà saisi dans le formulaire désormais défaillant.

La correction multiplateforme qui fonctionne réellement

Pour les annonces dans un WebView, le seul schéma multiplateforme fiable en 2026 est d’exposer un bridge JavaScript vers l’application hôte — un petit gestionnaire postMessage — et de router les annonces assertives hors du DOM, vers l’application hôte, puis à travers UIAccessibility.post(notification: .announcement, …) sur iOS ou announceForAccessibility() sur Android. L’aria-live web ne survit que pour les messages véritablement polis où un délai de quelques secondes est acceptable.


5. La table de correspondance — qui correspond à quoi

Nous avons cartographié 28 primitifs que VoiceOver et TalkBack consomment réellement en pratique — l’union de la surface du protocole iOS UIAccessibility, de la surface Android AccessibilityNodeInfo et des props multiplateformes les plus utilisées de React Native et Flutter. La table ci-dessous ne capture que les lignes contestées : les primitifs où le mapping est incomplet, asymétrique ou surprenant. Les lignes où le mapping est propre (label, rôle bouton, rôle image, titre) ont été omises pour des raisons de longueur.

CapacitéiOS UIAccessibilityAndroid AccessibilityNodeInfoReact Native 0.76Flutter 3.27
Texte hint (explication longue)accessibilityHinttooltipText (API 28+)accessibilityHint (iOS uniquement)SemanticsProperties.hint
Politesse live regionN/A — post impératif uniquementsetAccessibilityLiveRegion()accessibilityLiveRegion (Android uniquement)SemanticsFlag.isLiveRegion
Masquer le sous-arbre de l’a11yaccessibilityElementsHidden (enfants uniquement)importantForAccessibility=“noHideDescendants”accessibilityElementsHidden / importantForAccessibilityWidget ExcludeSemantics
Action personnalisée (rotor / menu)UIAccessibilityCustomActionAccessibilityNodeInfo.AccessibilityActionaccessibilityActions + onAccessibilityActionSemanticsAction avec label personnalisé
Semantics ajustable / curseurUIAccessibilityTraitAdjustable + accessibilityIncrementRangeInfo + ACTION_SCROLL_FORWARDaccessibilityRole=“adjustable” + gestionnairesSlider expose SemanticsAction.increase
Niveau de titreUIAccessibilityTraitHeader (sans niveau)setHeading(true) (sans niveau)accessibilityRole=“header” (sans niveau)SemanticsProperties.headingLevel (1–6)
État sélectionné / basculéUIAccessibilityTraitSelectedsetSelected(true) + setCheckable()accessibilityState={selected, checked}SemanticsFlag.isSelected
Semantics groupe / conteneurshouldGroupAccessibilityChildrensetScreenReaderFocusable(true)accessible={true} sur le parentWidget MergeSemantics
Annoncer un message ponctuelUIAccessibility.post(.announcement, …)view.announceForAccessibility()AccessibilityInfo.announceForAccessibility()SemanticsService.announce()

Trois schémas ressortent de la table. Premièrement, l’asymétrie autour des live regions est la principale source de divergence multiplateforme — Android dispose d’un paramètre de politesse par vue, iOS n’a qu’un post impératif global, et chaque framework ci-dessus est contraint de mentir sur la différence. Deuxièmement, les niveaux de titres sont le seul endroit où Flutter améliore réellement les deux plateformes natives ; les primitifs iOS et Android ne savent que « c’est un titre », pas « c’est un H3 sous un H2 ». Troisièmement, le primitif « masquer de l’accessibilité » est plus flexible sur Android que sur iOS — noHideDescendants masque tout un sous-arbre en un seul geste, tandis qu’iOS nécessite de masquer les enfants de chaque conteneur individuellement.


6. Le guide pratique mobile native

1

Apprendre le vocabulaire natif avant le vocabulaire du framework

Chaque bridge multiplateforme — React Native, Flutter, Compose Multiplatform — a sa propre nomenclature pour les props d’accessibilité, et chacun de ces noms est un léger mensonge sur ce que la plateforme sous-jacente fait réellement. Quand un lecteur d’écran n’annonce pas correctement, le bug se trouve presque toujours dans l’API native vers laquelle le framework a traduit, et non dans la prop du framework que l’on a définie. Il faut lire la documentation UIAccessibility et la documentation AccessibilityNodeInfo au moins une fois ; la documentation du framework n’a de sens qu’ensuite.

2

Tester les annonces live sur iOS spécifiquement

L’asymétrie de live region de la section 2 signifie que tout code qui suppose que aria-live=“assertive” ou accessibilityLiveRegion=“assertive” fonctionne va se dégrader silencieusement sur iOS. Il faut construire un petit banc de test qui déclenche une annonce polie et une annonce assertive sur les deux plateformes, avec VoiceOver et TalkBack sur de vrais appareils, avant de livrer toute fonctionnalité dont l’expérience utilisateur dépend de ce que l’utilisateur entende un changement d’état.

3

Sortir des WebViews via un bridge pour tout ce qui est assertif

La rétrogradation par WKWebView des annonces assertives n’est pas un bug qu’Apple corrigera prochainement — c’est la même chose dans chaque version iOS depuis la version 14. Si une application hybride est livrée où l’utilisateur peut rencontrer une erreur fatale dans un WebView, l’annonce doit être routée via un bridge JS vers l’hôte et laisser l’hôte déclencher l’annonce plateforme. Le web seul ne suffit pas.

4

Utiliser la sémantique « merge » ou « group » du framework, pas enfant par enfant

iOS (shouldGroupAccessibilityChildren), Android (setScreenReaderFocusable) et Flutter (MergeSemantics) fournissent tous un moyen de regrouper un cluster visuel — une icône plus un label plus une valeur — en un seul élément d’accessibilité. Il faut l’utiliser. Le comportement par défaut « chaque feuille est un élément focalisable » transforme une puce de navigation à six éléments en six balayages VoiceOver.

5

Auditer avec Accessibility Inspector et les paramètres développeur TalkBack

Les deux plateformes fournissent un inspecteur officiel gratuit pour l’arbre d’accessibilité en direct — Accessibility Inspector sur macOS (couplé au simulateur ou à l’appareil iOS connecté), et la superposition « Afficher le focus d’accessibilité » plus « Paramètres développeur » sur Android. Il faut les utiliser pour lire l’arbre de sa propre application comme le lecteur d’écran le voit ; il ne faut pas supposer que le journal de débogage du framework montre la même chose que ce que la plateforme montre à TalkBack.


Conclusion : le framework est en aval de la plateforme

Il est tentant de croire — et la documentation du framework encourage cette croyance — qu’une API d’accessibilité multiplateforme est une abstraction unique et unifiée au-dessus de deux API natives équivalentes. La table de correspondance de la section 5 réfute cette unification. Les deux API natives ont été conçues indépendamment, par deux équipes différentes, autour de deux modèles mentaux différents de la façon dont le lecteur d’écran devrait parcourir un document ; les différences sont réelles, elles filtrent à travers chaque framework, et les fuites apparaissent dans les parties de l’expérience utilisateur qui comptent le plus — les mises à jour live, les gestes personnalisés, les sous-arbres masqués, les hiérarchies de titres.

La bonne nouvelle, après ce paragraphe : les bases se mappent. Un bouton avec un label, une image avec un texte alternatif, un titre en haut d’une section — ces éléments passent à travers chaque framework et s’annoncent correctement sur les deux plateformes. Si l’on ne livre que ces primitifs, il n’est pas nécessaire de penser à UIAccessibility ou AccessibilityNodeInfo ; les valeurs par défaut du framework sont honnêtes. Les problèmes commencent quand l’interface utilisateur commence à faire quelque chose d’intéressant, ce qui est aussi le moment où l’accessibilité commence à compter le plus.

Le guide pratique de la section 6 est la version la plus courte de l’argument qui donne à davantage d’utilisateurs handicapés une expérience fonctionnelle : penser d’abord en primitifs natifs, tester sur de vrais appareils sur les deux plateformes, sortir des WebViews quand on le souhaite vraiment, regrouper délibérément les nœuds feuilles, et utiliser les inspecteurs officiels. Le framework choisi aide pour les 80 premiers pour cent et s’efface pour les 20 derniers. Ces 20 derniers pour cent sont là où vit l’utilisateur de lecteur d’écran.

« VoiceOver et TalkBack lisent deux documents différents à partir du même code source. Que l’utilisateur remarque la différence est une mesure de la qualité de la compréhension de la plateforme sous-jacente au framework. »

— Bureau engineering de Disability World, mai 2026