Addaw, anar a la pàgina d'inici Donar

Lliçó 8 de 10 · 12 min de lectura · Revisada el 26/10/2026

Aplicacions híbrides i multiplataforma: React Native, Flutter i WebView

Estàs llegint una lliçó oberta. Matricula't per fer els tests i els exercicis, desar el teu progrés i aconseguir el certificat.

Matricula-m'hi · nivell 6 (119 €)

Bona part de les aplicacions que s'auditen a Espanya no estan escrites en Swift ni en Kotlin, sinó en React Native, Flutter, Ionic/Capacitor, .NET MAUI o directament com un web empaquetat. Cada tecnologia té la seva pròpia manera d'alimentar l'API d'accessibilitat nativa, i cadascuna té punts cecs. Aquesta lliçó resumeix què ofereix cada framework, com es fa servir i quins errors produeix amb més freqüència.

El principi comú: traduir a la plataforma

Totes aquestes tecnologies acaben parlant amb UIAccessibility a iOS i amb AccessibilityNodeInfo a Android. La pregunta per a cada component és: quin nom, rol, estat, valor i accions arriben al servei d'accessibilitat natiu? Si el framework no els tradueix, VoiceOver o TalkBack veuran un rectangle mut. Per això la verificació s'ha de fer sempre amb les tecnologies de suport reals en tots dos sistemes, mai només a l'emulador web o amb l'eina d'inspecció del framework.

React Native

React Native exposa props d'accessibilitat als seus components bàsics (View, Text, Pressable, TouchableOpacity, Image…) que es mapen a totes dues plataformes:

  • accessible={true}: agrupa la vista i els seus fills en un sol element accessible (equivalent a isAccessibilityElement / mergeDescendants). Pressable i els touchables l'activen per defecte.
  • accessibilityLabel: el nom. Obligatori als botons d'icona i a les imatges informatives.
  • accessibilityRole: button, link, header, image, checkbox, switch, tab, adjustable, alert, summary…
  • accessibilityState: {disabled, selected, checked, busy, expanded}; accessibilityValue: {min, max, now, text}.
  • accessibilityHint, accessibilityActions + onAccessibilityAction per a les accions personalitzades.
  • accessibilityLiveRegion (Android) i AccessibilityInfo.announceForAccessibility() (totes dues) per als missatges d'estat.
  • importantForAccessibility="no-hide-descendants" (Android) i accessibilityElementsHidden (iOS) per amagar; accessibilityViewIsModal (iOS) per als diàlegs.
  • AccessibilityInfo.isScreenReaderEnabled() i isReduceMotionEnabled() per adaptar comportaments.
<Pressable
  accessibilityRole="button"
  accessibilityLabel={`${m.concepto}, ${m.importeAccesible}, ${m.fechaLarga}`}
  accessibilityHint="Obre el detall del moviment"
  accessibilityActions={[{ name: 'categorizar', label: 'Categoritza' }]}
  onAccessibilityAction={(e) => e.nativeEvent.actionName === 'categorizar' && categorizar(m)}
  onPress={() => abrir(m)}
  style={{ minHeight: 48 }}
>
  <Image source={m.icono} accessible={false} />
  <Text>{m.concepto}</Text>
  <Text>{m.importeFormateado}</Text>
</Pressable>

Errors habituals a React Native: TouchableOpacity embolcallant una icona sense accessibilityLabel; capçaleres de secció sense accessibilityRole="header"; modals de biblioteques de tercers que no bloquegen el focus al darrere; text amb allowFontScaling={false} (desactiva l'escalat i trenca l'1.4.4) o amb un maxFontSizeMultiplier massa baix; biblioteques de navegació que no anuncien el canvi de pantalla (comprova que el títol de la pantalla rep el focus o s'anuncia). Les eines eslint-plugin-react-native-a11y i les proves amb react-native-testing-library ajuden a detectar omissions.

Flutter

Flutter dibuixa tots els píxels amb el seu propi motor; no fa servir vistes natives. Per a l'accessibilitat construeix un arbre de semàntica paral·lel que sincronitza amb la plataforma. Els widgets de Material i Cupertino l'alimenten per defecte; els personalitzats necessiten el widget Semantics:

Semantics(
  label: '${m.concepto}, ${m.importeAccesible}, ${m.fechaLarga}',
  hint: 'Obre el detall del moviment',
  button: true,
  onTap: () => abrir(m),
  customSemanticsActions: {
    CustomSemanticsAction(label: 'Categoritza'): () => categorizar(m),
  },
  child: ExcludeSemantics(      // els fills no generen nodes propis
    child: Row(children: [
      Image.asset(m.icono),
      Text(m.concepto),
      Text(m.importeFormateado),
    ]),
  ),
);

// Encapçalament
Semantics(header: true, child: Text('Moviments de març'));

// Imatge informativa
Image.asset('grafico.png', semanticLabel: 'Despeses per categoria: alimentació 40 %…');

// Anunci d'estat
SemanticsService.announce('Cita confirmada', TextDirection.ltr);

Altres elements: MergeSemantics per unir els fills en un node, ExcludeSemantics per amagar, Semantics(sortKey: OrdinalSortKey(n)) per a l'ordre, liveRegion: true per als missatges d'estat, i MediaQuery.textScalerOf(context) per respectar l'escalat (els Text ho fan per defecte llevat que es fixi textScaler). La marca showSemanticsDebugger: true a MaterialApp mostra l'arbre de semàntica sobre la interfície. Punts febles freqüents: GestureDetector o InkWell sobre contenidors sense Semantics(button: true), Icon sense semanticLabel, llistes llargues amb widgets personalitzats que no exposen la posició, i la versió web de Flutter, l'accessibilitat de la qual depèn d'un arbre DOM «semàntic» generat que s'ha d'activar i verificar.

Punt clau

A Flutter i a qualsevol motor que pinti els seus propis píxels, l'accessibilitat no s'hereta de res: si l'arbre de semàntica no descriu un element, aquest element no existeix per a TalkBack ni per a VoiceOver. L'inspector de semàntica ha de formar part de la rutina de desenvolupament.

Aplicacions en WebView: Ionic, Capacitor, Cordova i PWA empaquetades

Si la interfície és HTML dins d'un WKWebView o WebView, s'apliquen íntegrament les WCAG 2.2 web: HTML semàntic, ARIA, focus, contrast. Les tecnologies de suport natives llegeixen el DOM per mitjà del motor del navegador. Particularitats:

  • L'escalat del text del sistema pot no aplicar-se al WebView: a Android, WebSettings.textZoom ha de seguir l'escala de lletra del sistema (moltes aplicacions el fixen al 100 %); a iOS, el CSS ha de fer servir font: -apple-system-body (i mides relatives) per seguir Dynamic Type.
  • Les barres i els controls natius que embolcallen el WebView (pestanyes, botó enrere) segueixen les regles natives; l'ordre del focus entre la part nativa i la web ha de ser coherent.
  • Els connectors de càmera, biometria o notificacions natives han de tenir una alternativa i una resposta accessible.
  • Ionic Framework inclou components amb un ARIA raonable, però les versions antigues i els components personalitzats requereixen una auditoria com qualsevol web.
  • El títol de la «pantalla» (2.4.2) s'ha d'anunciar en navegar: en una SPA dins d'un WebView, gestiona el focus i el document.title com en qualsevol SPA.

.NET MAUI i altres

MAUI exposa SemanticProperties.Description, SemanticProperties.Hint, SemanticProperties.HeadingLevel i SemanticScreenReader.Announce(); AutomationProperties.IsInAccessibleTree permet amagar. Kotlin Multiplatform amb Compose Multiplatform hereta la semàntica de Compose a Android i la tradueix a UIAccessibility a iOS, amb una cobertura encara en evolució; convé verificar cada versió. Unity i altres motors de jocs no tenen un suport natiu complet i requereixen connectors específics.

Bones pràctiques comunes

  1. Prefereix els components del framework abans que els controls pintats a mà; hereten el rol, l'estat i les accions a totes dues plataformes.
  2. Defineix una capa de components accessibles pròpia (botó, camp, targeta, capçalera) que ja porti les props correctes, i prohibeix l'ús directe de primitives sense aquestes props.
  3. Prova en un dispositiu real a iOS i Android amb VoiceOver i TalkBack; els mapatges difereixen i un error pot aparèixer només en una plataforma.
  4. No desactivis l'escalat de lletra global; adapta la disposició.
  5. Audita les biblioteques de tercers (carrusels, selectors de data, gràfics, modals) abans d'adoptar-les: són l'origen de la majoria d'errors no corregibles.
  6. Automatitza el que puguis: regles de lint d'accessibilitat, proves que verifiquin l'existència d'etiquetes i rols, i els auditors de Xcode/Android al pipeline (ho veuràs a la lliçó «L'accessibilitat en el flux de desenvolupament» d'aquest curs i al curs «Avaluació d'aplicacions mòbils»).

Error freqüent

Donar per accessible una aplicació híbrida perquè «el web passa axe». axe no veu la barra de pestanyes nativa sense etiquetes, el WebView amb textZoom fixat al 100 %, ni la manca d'anunci en canviar de pantalla. L'auditoria d'una aplicació híbrida combina la metodologia web (dins del WebView) amb la nativa (tot el que l'envolta).

Exemple

Una aplicació de comerç electrònic en React Native feia servir una biblioteca de carrusel amb avanç automàtic i fletxes sense etiqueta. Es va substituir per un component propi: accessibilityRole="adjustable" amb accessibilityValue «Producte 2 de 6», accions «incrementar/decrementar» mapades a següent/anterior, avanç automàtic desactivat quan isScreenReaderEnabled o isReduceMotionEnabled i un botó de pausa visible (2.2.2). El mateix component va funcionar a iOS i Android sense codi específic.

Resum

  • Totes les tecnologies multiplataforma acaben alimentant UIAccessibility i AccessibilityNodeInfo; la pregunta sempre és quin nom, rol, estat i accions arriben al servei natiu.
  • React Native: accessible, accessibilityLabel/Role/State/Value/Hint/Actions, live regions i announceForAccessibility; no desactivis allowFontScaling.
  • Flutter: widget Semantics (label, hint, button, header, liveRegion, custom actions), MergeSemantics/ExcludeSemantics, SemanticsService.announce i el depurador de semàntica.
  • WebView: les WCAG web íntegres més l'escalat del text del sistema i la coherència amb la part nativa.
  • Capa de components accessibles pròpia, auditoria de les biblioteques de tercers i proves en dispositiu real a totes dues plataformes.

T'ha servit aquesta lliçó?

Crea el teu compte i segueix el curs amb tot:

  • El test d'aquesta lliçó (3 preguntes) per comprovar el que has après
  • Exercicis pràctics amb pàgines reals
  • El teu progrés desat: continues on ho vas deixar, des de qualsevol dispositiu
  • Constància del curs amb les hores i certificat del nivell 6 quan aprovis l'examen

Entres amb el teu compte de Google o només amb el teu correu electrònic. No cal contrasenya.

Matricula-m'hi · nivell 6 (119 €)