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 · Mobile a11y APIs

Mobile-native tilgængeligheds-API'er i 2026: UIAccessibility, AccessibilityNode og nettet

En sammenlignende primer på iOS UIAccessibility, Android AccessibilityNodeInfo og de tværplatformbrygger, der forsøger at forene dem — hvad der mapper rent, hvad der ikke gør, og hvor mobiltilgængelighed stille fejler.

Mobile-native tilgængeligheds-API’er i 2026:
UIAccessibility, AccessibilityNode og nettet

iOS og Android eksponerer begge et fuldt udbygget tilgængelighed-træ til platformens skærmlæser — og de to træer er ikke enige om noget ud over label-og-rolle-grundlaget. Vi kortlagde alle primitiver, som VoiceOver og TalkBack faktisk forbruger i 2026, den måde React Native, Flutter og Kotlin Multiplatform forsøger at bygge bro, og det sted, hvor mobil WebView-tilgængelighed stille falder af en klippe.

2
native API’er sammenlignet
3
tværplatformbrygger
28
primitiver kortlagt
13 min. læsning
Opdateret maj 2026

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

Alt synligt på en iOS-skærm har — eller kan have — en tilgængelighed-repræsentation. Apple leverer den repræsentation via den uformelle protokol UIAccessibility, implementeret af UIView og alle systemkontroller, og via UIAccessibilityElement, en letvægtklasse, man allokerer til de dele af grænsefladen, der er tegnet men ikke i sig selv er views — tegn i et brugerdefineret diagram, glyffer i en Core Graphics-scene, regioner inde i et CALayer. VoiceOver, Switch Control, Full Keyboard Access og Voice Control forbruger alle den samme protokol; at lære den én gang giver adgang til fire hjælpeteknologier.

Protokollen eksponerer fire primitiver, der er vigtige for næsten alle skærme. Tilgængeligheds-labelet er elementets korte, menneskelig-læsbare navn — “Send”, “Profilbillede af Asha”, “Tilbage”. Tilgængeligheds-traits er en bitmask af rollelignende flag — .button, .header, .image, .selected, .adjustable, .staticText, .updatesFrequently — der fortæller VoiceOver, hvordan det skal opføre sig over for elementet, og hvilke gestikker det skal aktivere. Tilgængeligheds-value er en strengrepræsentation af den aktuelle tilstand (“On”, “75%”, “torsdag den 22. maj”). Tilgængeligheds-hintet er den længere, valgfrie forklaring (“Dobbelt-tryk for at åbne fotofremviseren”), som VoiceOver udtaler med forsinkelse, hvis brugeren ikke handler på labelet alene.

De fire primitiver sammensættes. En knap til/fra læses som label + trait + value: “Wi-Fi, afbryderknap, On”. En skyder læses som label + trait + value + hint: “Lydstyrke, justerbar, 60 procent, stryg op eller ned for at justere”. En brugerdefineret diagrambjælke læses som en kæde af UIAccessibilityElementer, hver med et label, en value og en ramme inde i sin container. Kæden er API-fladen — VoiceOver gennemløber den lineært, når brugeren stryger til højre, og respekterer den rækkefølge, hvori elementerne publiceres via containerens accessibilityElements-array.

SwiftUI er den samme protokol med en venligere facade

View-modifikatorerne .accessibilityLabel(), .accessibilityValue(), .accessibilityHint() og .accessibilityAddTraits() i SwiftUI kompilerer ned til de samme UIAccessibility-egenskaber på den underliggende UIView. SwiftUI tilføjer også .accessibilityElement(children:), som løser “tegn i et diagram”-problemet mere deklarativt end UIKit-tilgangen — men den runtime-kontrakt, VoiceOver ser, er identisk. Det er stadig værd at lære UIKit-navnene, fordi alle Apples eksempler, alle Stack Overflow-svar og alle tilgængelighed-audits taler i dem.


2. Android AccessibilityNodeInfo — roller, handlinger, importantForAccessibility

Android tager en anden rute. Hvor iOS hænger tilgængelighed op på en flad protokol på hvert view, serialiserer Android hele tilgængelighed-træet som en graf af AccessibilityNodeInfo-objekter, hvert et snapshot af et view i det øjeblik, en TalkBack-forespørgsel ankommer. Frameworket konstruerer snapshotterne dovent; et View publicerer sin node ved at tilsidesætte onInitializeAccessibilityNodeInfo() (eller, i Compose, ved at sætte semantics-modifikatorer), og platformen syr forældre-barn-relationer til et træ, der spejler view-hierarkiet.

Primitiverne adskiller sig fra iOS på tre meningsfulde måder. For det første eksponerer Android en rolle via et strengtypet className-felt — android.widget.Button, android.widget.CheckBox, android.widget.EditText. TalkBack læser klassenavnet og beslutter, hvordan det skal annonceres (“knap”, “afkrydsningsfelt”, “redigeringsfelt”). Compose oversætter sine Role.Button-, Role.Checkbox-, Role.RadioButton-semantik til det samme felt. Rollen er mere granulær end en iOS trait-bitmask, men også mere rigid — der er ingen “fuldt brugerdefineret” rolle, medmindre man accepterer annonceringen som “view”.

For det andet repræsenterer Android interaktivitet som et sæt handlinger knyttet til noden: ACTION_CLICK, ACTION_LONG_CLICK, ACTION_SCROLL_FORWARD, ACTION_SET_TEXT, ACTION_FOCUS og en lang liste af brugerdefinerede handlinger, man kan registrere med AccessibilityNodeInfo.AccessibilityAction. TalkBack eksponerer de brugerdefinerede handlinger via “handlinger”-rotoren — brugeren stryger op med én finger og hører hver brugerdefineret handling ved navn. iOS har det samme koncept (UIAccessibilityCustomAction), men på Android er handlingslisten fladen; på iOS er gestik-vokabularet det.

For det tredje har Android importantForAccessibility, en per-view-enum (auto, yes, no, noHideDescendants), der styrer, om noden overhovedet optræder i træet. noHideDescendants er det mest kraftfulde enkeltværktøj i Android-tilgængelighed og det, der oftest glemmes — det fjerner hele undertræet fra TalkBacks gennemløb, svarende til aria-hidden=“true” på nettet. iOS har ingen nøjagtig pendant; det nærmeste er at sætte accessibilityElements til et tomt array på containeren, som kun fjerner containerens direkte børn, ikke hele undertræet.

Uoverensstemmelsen om “live region”

Android eksponerer ViewCompat.setAccessibilityLiveRegion() med tre værdier: none, polite, assertive. Vokabularet spejler ARIA — næsten. TalkBack overholder politeness-niveauerne pålideligt. iOS har intet sammenligneligt på protokolniveauet: opdateringer annonceres ved at kalde UIAccessibility.post(notification: .announcement, argument: “Gemt”), en imperativ én-skudsbesked, der ikke er knyttet til et view. Tværplatformbrygger er nødt til at simulere den ene ovenpå den anden, og impedans-uoverensstemmelsen viser sig i hvert framework gennemgået i afsnit 3.


3. Tværplatformbrygger — React Native, Flutter, Kotlin Multiplatform

Ethvert tværplatform mobilframework er nødt til at tage de to API’er ovenfor og præsentere en enkelt, framework-formet flade. Ingen af dem lykkes fuldstændigt. De tre tilgange dominerer markedet i 2026 — React Native, Flutter og Kotlin Multiplatform med Compose Multiplatform — hver en lidt anden handel mellem læk og abstraktion.

React Native 0.76
JS-bro til native UIKit og Android View
Den mest eksplicitte mapping — og den mest utætte
iOS-broaccessibilityLabel, accessibilityHint, accessibilityRole, accessibilityStatePressable og View mapper næsten 1:1 til UIAccessibility — men rollenavnene er React Native-vokabularet, ikke iOS-vokabularet.
Android-broDe samme JS-props mapper til AccessibilityNodeInfo via en Yoga-side-adapter; accessibilityRole=“button” sætter className til android.widget.Button.
FaldgrubeaccessibilityLiveRegion-propen er kun Android — på iOS gør den stille ingenting, og man er nødt til at kalde AccessibilityInfo.announceForAccessibility() manuelt.
Flutter 3.27
Brugerdefineret gengivelse · syntetisk a11y-træ
Den mest ensartede — og den mest uigennemsigtige
TilgangFlutter gengiver alt på et Skia-lærred og bygger derfor sit eget SemanticsNode-træ og serialiserer det til platformen efter behov.
iOS-stiSemanticsNodes oversættes til UIAccessibilityElement-instanser på Flutter-viewet, med traits mappet fra SemanticsAction- og SemanticsFlag-sæt.
Android-stiDet samme SemanticsNode-træ serialiseres til AccessibilityNodeInfo-noder af Flutters Android-view; handlinger bliver AccessibilityActions; live region bliver SemanticsFlag.isLiveRegion.
Kotlin Multiplatform · Compose Multiplatform
Delt Compose-runtime · per-target a11y
Den nyeste, med de mest platformformede sømme
TilgangComposes Modifier.semantics { } definerer roller og handlinger én gang; hvert target oversætter den samme semantics-blok til sit eget native a11y-API.
iOS-targetCompose-for-iOS-runtimen gennemløber semantics-træet og konstruerer UIAccessibilityElements — men iOS-implementeringen er yngre end Androids og mangler stadig adskillige semantiske typer.
Android-targetDen modne sti: semantics bliver AccessibilityNodeInfo via det samme compose-ui-semantics-lag, som native Android-Compose bruger.

Mønsteret på tværs af alle tre er det samme: et syntetisk, framework-formet semantisk træ på den ene side, to platformformede tilgængelighed-træer på den anden, og en oversætter imellem, der håndterer de simple tilfælde godt og de komplekse med et mærkbart tab af trouvailles. De simple tilfælde — en knap med et label, et billede med alternativ tekst, en overskrift — round-tripper uden tab. De komplekse tilfælde — en brugerdefineret gestik med to-fingersstrygning, et diagram, hvis elementer bør være en fokuserbar gruppe, en live region, der skal aktiveres på iOS uden en view-bundet politeness-indstilling — lækker det underliggende platforms vokabular op i tværplatformkoden eller fejler simpelthen i oversættelsen.

„De første 80 procent af mobiltilgængelighed er identiske på tværs af alle frameworks. De sidste 20 procent er der, hvor hvert framework afslører, hvilket native API det i hemmelighed tænker i.“

— Disability Worlds ingeniørdesk, maj 2026

4. WebView-kløften — når mobil-webtilgængelighed stille fejler

Både iOS og Android gengiver webindhold via en system-WebView — WKWebView på iOS, android.webkit.WebView (eller Chrome Custom Tabs) på Android. I begge tilfælde er WebView en sort boks fra værtappens perspektiv: appen ser ét view, men skærmlæseren ser hele DOM-tilgængelighed-træet inde i det. Broen mellem de to træer er det sted, hvor en overraskende stor mængde mobiltilgængelighed går stille galt.

Mekanismen er, ved første øjekast, ligetil. Når en skærmlæsers fokus entrer en WebView, læser platformen dokumentets tilgængelighed-træ direkte fra browsermotoren — WebKit på iOS, Blink på Android — og gennemløber det som et undertræ af værtappens træ. Nettets roller, labels og ARIA-attributter oversættes til platformens vokabular i realtid. Et button-element uden eksplicit rolle inde i WebView læses som en knap på begge platforme; en aria-live=“polite”-region annoncerer korrekt på begge; en aria-label på et link fremkommer som linkens tilgængelighed-label. De første tre år af mobilt webliv fungerede dette bare.

Klippen dukker op tre steder. For det første er brugerdefinerede gestikker defineret i værtappen — en to-fingersstrygning for at afvise, et magic-tap for at afspille og sætte på pause — usynlige for WebViewens indhold; de aktiveres på det forkerte mål eller aktiveres slet ikke, når fokus er inde i dokumentet. For det andet konkurrerer værtappens UIAccessibilityElementer tegnet over WebView (en flydende handlingsknap, en brugerdefineret værktøjslinje) med WebViewens træ om gennemløbsrækkefølgen, og den resulterende læserækkefølge er ikke-deterministisk på tværs af iOS-versioner. For det tredje — og dette er den største enkeltfejlmode i mobil-webtilgængelighed — overholder WebView på iOS ikke aria-live politeness-niveauer på den måde, Safari gør på en fane: WKWebViews annonceringssystem dropper skelnen mellem polite og assertive, så alle live-opdateringer behandles som polite uanset markuppet.

To synsvinkler på den samme DOM
I en Mobile Safari-fane
<div role="alert" aria-live="assertive">
  Forbindelsen mistet — prøver igen.
</div>

VoiceOver i en normal Safari-fane afbryder den aktuelle ytring og udtaler beskeden straks. assertive politeness overholdes fra ende til anden via WebKit.

Inde i den samme DOM i en WKWebView
<div role="alert" aria-live="assertive">
  Forbindelsen mistet — prøver igen.
</div>

Samme markup, samme browsermotor — men WKWebViews tilgængelighed-bro til UIKit nedgraderer annonceringen til en udsat polite-besked. Brugeren hører den med forsinkelse, sommetider efter de allerede har tastet ind i den nu-ødelagte formular.

Den tværplatformrettelse, der faktisk virker

For annonceringer inde i en WebView er det eneste pålidelige tværplatformmønster i 2026 at eksponere en JavaScript-bro til værtappen — en lille postMessage-handler — og route assertive-annonceringer ud af DOM, ind i værtappen og tilbage via UIAccessibility.post(notification: .announcement, …) på iOS eller announceForAccessibility() på Android. Nettets aria-live overlever kun til genuint høflige beskeder, hvor et par sekunders forsinkelse er acceptabel.


5. Kortlægningstabellen — hvad svarer til hvad

Vi kortlagde 28 primitiver, som VoiceOver og TalkBack faktisk forbruger i praksis — fagforeningen af iOS UIAccessibility-protokolfladen, Android AccessibilityNodeInfo-fladen og de mest brugte React Native- og Flutter-tværplatformprops. Tabellen nedenfor fanger kun de omstridte rækker: de primitiver, hvor kortlægningen er ufuldstændig, asymmetrisk eller overraskende. Rækker, hvor kortlægningen er ren (label, knapperoll, billedrolle, overskrift), er udeladt af pladshensyn.

KapabilitetiOS UIAccessibilityAndroid AccessibilityNodeInfoReact Native 0.76Flutter 3.27
Hint-tekst (længere forklaring)accessibilityHinttooltipText (API 28+)accessibilityHint (kun iOS)SemanticsProperties.hint
Live region politenessN/A — kun imperativ postsetAccessibilityLiveRegion()accessibilityLiveRegion (kun Android)SemanticsFlag.isLiveRegion
Skjul undertræ fra a11yaccessibilityElementsHidden (kun børn)importantForAccessibility=“noHideDescendants”accessibilityElementsHidden / importantForAccessibilityExcludeSemantics-widget
Brugerdefineret handling (rotor/menu)UIAccessibilityCustomActionAccessibilityNodeInfo.AccessibilityActionaccessibilityActions + onAccessibilityActionSemanticsAction med brugerdefineret label
Justerbar/skyder-semantikUIAccessibilityTraitAdjustable + accessibilityIncrementRangeInfo + ACTION_SCROLL_FORWARDaccessibilityRole=“adjustable” + handlersSlider eksponerer SemanticsAction.increase
OverskriftsniveauUIAccessibilityTraitHeader (intet niveau)setHeading(true) (intet niveau)accessibilityRole=“header” (intet niveau)SemanticsProperties.headingLevel (1–6)
Valgt/skiftet tilstandUIAccessibilityTraitSelectedsetSelected(true) + setCheckable()accessibilityState={selected, checked}SemanticsFlag.isSelected
Gruppe/container-semantikshouldGroupAccessibilityChildrensetScreenReaderFocusable(true)accessible={true} på forælderMergeSemantics-widget
Annoncer én-skudsbeskedUIAccessibility.post(.announcement, …)view.announceForAccessibility()AccessibilityInfo.announceForAccessibility()SemanticsService.announce()

Tre mønstre springer frem af tabellen. For det første er asymmetrien omkring live regions den største enkeltårsag til tværplatform-divergens — Android har en per-view politeness-indstilling, iOS har kun en global imperativ post, og alle frameworks ovenfor er tvunget til at lyve om forskellen. For det andet er overskriftsniveauer det ene sted, Flutter faktisk forbedrer på begge native platforme; iOS- og Android-primitiverne ved kun “dette er en overskrift”, ikke “dette er en H3 under en H2”. For det tredje er “skjul fra tilgængelighed”-primitiven mere fleksibel på Android end på iOS — noHideDescendants skjuler et helt undertræ i ét træk, mens iOS kræver, at man skjuler hvert containers børn individuelt.


6. Spillebogen for mobile-native

1

Lær det native vokabular før framework-vokabularet

Enhver tværplatformsbro — React Native, Flutter, Compose Multiplatform — har sit eget navngivning for tilgængelighed-props, og hvert af disse navne er en lille løgn om, hvad den underliggende platform faktisk gør. Når en skærmlæser ikke annoncerer korrekt, lever fejlen næsten altid i det native API, frameworket oversatte til, ikke i framework-propen man satte. Læs UIAccessibility-dokumentationen og AccessibilityNodeInfo-dokumentationen mindst én gang; framework-dokumenterne giver kun mening bagefter.

2

Test live-annonceringer på iOS specifikt

Live-region-asymmetrien fra afsnit 2 betyder, at enhver kode, der antager, at aria-live=“assertive” eller accessibilityLiveRegion=“assertive” virker, vil degradere stille på iOS. Byg en lille testramme, der aktiverer både en polite og en assertive annoncering på begge platforme, med VoiceOver og TalkBack på rigtige enheder, inden man leverer en funktion, hvis UX afhænger af, at brugeren hører en tilstandsændring.

3

Bro ud af WebViews for alt assertive

WKWebViews nedgradering af assertive-annonceringer er ikke en fejl, Apple vil rette snart — det har været det samme i alle iOS-versioner fra 14 og frem. Hvis man leverer en hybrid-app, hvor brugeren kan støde på en fatal fejl inde i en WebView, skal man route annonceringen via en JS-bro til værtappen og lade værtappen aktivere platformsannonceringen. Nettet alene er ikke nok.

4

Brug frameworkets “merge”- eller “gruppe”-semantik, ikke børn-for-børn

Både iOS (shouldGroupAccessibilityChildren), Android (setScreenReaderFocusable) og Flutter (MergeSemantics) giver en måde at sammensætte et visuelt klynge — et ikon plus et label plus en value — til et enkelt tilgængelighed-element. Brug det. Standardadfærden “hvert blad er et fokuserbart element” gør en seks-element navigationschip til seks VoiceOver-strygninger.

5

Auditér med Accessibility Inspector og TalkBack Developer Settings

Begge platforme leverer en gratis, officiel inspektor for det live tilgængelighed-træ — Accessibility Inspector på macOS (parret med den tilsluttede iOS-simulator eller -enhed) og “Vis tilgængelighed-fokus” plus “Udviklerindstillinger”-overlayét på Android. Brug dem til at læse sin egen apps træ, som skærmlæseren ser det; antag ikke, at frameworkets fejllog viser det samme, platformen viser TalkBack.


Konklusion: frameworket er nedstrøms for platformen

Det er fristende at tro — og framework-dokumentationen opmuntrer til denne tro — at et tværplatform tilgængeligheds-API er en enkelt, samlet abstraktion over to ækvivalente native API’er. Kortlægningstabellen i afsnit 5 modbeviser foreningen. De to native API’er er designet uafhængigt af to forskellige teams, omkring to forskellige mentale modeller for, hvordan skærmlæseren bør gennemgå et dokument; forskellene er reelle, de lækker igennem alle frameworks, og lækagen viser sig i de dele af brugeroplevelsen, der betyder mest — live-opdateringer, brugerdefinerede gestikker, skjulte undertræer, overskrifthierarkier.

De gode nyheder, efter det afsnit: grundlaget mapper. En knap med et label, et billede med alternativ tekst, en overskrift øverst i et afsnit — de round-tripper igennem alle frameworks og annonceres korrekt på begge platforme. Hvis man kun leverer disse primitiver, behøver man ikke tænke på UIAccessibility eller AccessibilityNodeInfo; frameworkets standardindstillinger er ærlige. Problemerne starter, når brugergrænsefladen begynder at gøre noget interessant, hvilket også er, når tilgængelighed begynder at betyde mest.

Spillebogen i afsnit 6 er den korteste version af argumentet, der får de fleste mennesker med handicap til en fungerende oplevelse: tænk i native primitiver først, test på rigtige enheder på begge platforme, bro ud af WebViews, når det er meningen, grupper bladnoder bevidst og brug de officielle inspektorer. Det framework, man valgte, hjælper med de første 80 procent og holder sig ude af vejen for de sidste 20 procent. De sidste 20 procent er der, hvor skærmlæserbrugeren lever.

„VoiceOver og TalkBack læser to forskellige dokumenter fra den samme kildekode. Om brugeren bemærker forskellen er et mål for, hvor godt man forstod platformen under sit framework.“

— Disability Worlds ingeniørdesk, maj 2026