vgv-accessibility

Par verygoodopensource · vgv-ai-flutter-plugin

Audit et remédiation de l'accessibilité Flutter avec sélection du niveau de conformité WCAG 2.1 (A, AA, AAA) sur les plateformes mobile, desktop et web. Commence par demander le niveau de conformité WCAG et la ou les plateformes cibles avant d'appliquer des critères adaptés au niveau et à la plateforme.

npx skills add https://github.com/verygoodopensource/vgv-ai-flutter-plugin --skill vgv-accessibility

Accessibilité

Audit et correction de l'accessibilité Flutter selon les niveaux de conformité WCAG 2.1 A, AA et AAA — sémantique, zones tactiles, gestion du focus, contraste des couleurs, mise à l'échelle du texte et sensibilité au mouvement sur les plateformes mobile, desktop et web.


Normes fondamentales

Appliquez ces normes à TOUS les travaux d'accessibilité :

  • Commencez chaque audit en demandant à l'utilisateur quel niveau de conformité WCAG 2.1 il vise (A, AA ou AAA) — ne présumez jamais AA
  • Commencez chaque audit en demandant quelles plateformes sont ciblées (mobile, desktop, web) — le comportement du lecteur d'écran et les exigences au clavier diffèrent par plateforme
  • Chaque Image doit avoir semanticLabel ou être encapsulée dans Semantics(label:) — les images décoratives utilisent excludeFromSemantics: true
  • N'utilisez jamais GestureDetector pour les zones de tap — utilisez InkWell, ElevatedButton, TextButton ou IconButton ; GestureDetector est uniquement pointer et inaccessible via clavier ou accès par commutateur
  • Tous les éléments interactifs : zone tactile minimale de 48x48 dp — appliquez avec SizedBox, ConstrainedBox ou padding
  • N'utilisez jamais la couleur comme seul différenciateur — associez toujours la couleur à une étiquette, une icône ou une forme
  • Toutes les animations doivent respecter MediaQuery.disableAnimations — encadrez chaque AnimationController, AnimatedContainer et transition Hero sur cet indicateur
  • Les boutons avec icône uniquement doivent avoir Tooltip ou Semantics(label:) — les lecteurs d'écran n'ont pas d'autre moyen de communiquer le but
  • N'utilisez jamais ExcludeSemantics sur du contenu non-décoratif — cela masque les informations utiles aux technologies d'assistance
  • Les conteneurs à hauteur fixe ne doivent pas encapsuler Text — utilisez les contraintes minHeight ; les hauteurs fixes découpent le texte à l'échelle de police 1,5-2x
  • Tout le texte et les composants UI doivent respecter le ratio de contraste du niveau WCAG sélectionné — consultez la section Référence des critères de niveau WCAG ci-dessous pour les seuils spécifiques au niveau

Flux de travail

Chaque engagement d'accessibilité suit quatre phases en séquence. Ne sautez pas la Phase 1 ou la Phase 2.

Phase 1 — Sélection du niveau de conformité

Avant d'auditer ou d'écrire tout code d'accessibilité, posez la question :

« Quel niveau de conformité WCAG 2.1 visez-vous ?

  • A — Supprime les barrières les plus critiques. Couvre le texte alternatif, l'accès au clavier, l'absence de risques de convulsions, la structure de base et l'identification des erreurs.
  • AA — S'appuie sur le niveau A. Ajoute les ratios de contraste (4.5:1 / 3:1), les sous-titres, le redimensionnement du texte, l'apparence du focus et la navigation cohérente. C'est la norme légale et de conformité la plus courante.
  • AAA — Niveau le plus élevé. Ajoute le contraste amélioré (7:1), la langue des signes, les descriptions audio étendues et l'absence de limite de temps. L'AAA complet est rarement requis pour les produits entiers mais peut s'appliquer à des composants spécifiques.

Répondez par A, AA ou AAA. »

Enregistrez le niveau sélectionné. Tous les contrôles d'audit, les références de critères de rapport et les recommandations de correction ultérieurs n'appliquent que les règles de ce niveau (plus tous les niveaux en dessous).

Phase 2 — Sélection de la plateforme

Posez la question :

« Sur quelle(s) plateforme(s) cette app est-elle destinée ? Sélectionnez tout ce qui s'applique :

  • Mobile — iOS (VoiceOver) et/ou Android (TalkBack)
  • Desktop — macOS (VoiceOver), Windows (Narrator, NVDA, JAWS), Linux (Orca)
  • Web — basé sur navigateur ; NVDA+Chrome (Windows), JAWS+Chrome (Windows), VoiceOver+Safari (macOS)

Répondez par un ou plusieurs : Mobile, Desktop, Web. »

Enregistrez les plateformes sélectionnées. Appliquez les contrôles spécifiques à la plateforme lors de l'audit :

  • Mobile — tailles de zone tactile (48x48 dp), ordre de traversal sémantique TalkBack/VoiceOver, alternatives de geste pour toutes les interactions uniquement pointer
  • Desktop — navigation au clavier complète requise pour chaque interaction, indicateurs de focus toujours visibles, aucune interaction tactile uniquement
  • Web — blocs de contournement (2.4.1), titres de page (2.4.2), langue de la page (3.1.1), reflux à l'équivalent 320px (1.4.10), persistance du contenu au survol/focus (1.4.13)

Phase 3 — Audit adapté au niveau

Auditez les fichiers ou widgets fournis sur les six catégories suivantes, vérifiez uniquement les critères applicables au niveau sélectionné ET pertinents pour les plateformes sélectionnées. Pour chaque constatation, capturez : chemin du fichier et numéro de ligne approximatif, ID et nom du critère WCAG, plateforme(s) affectée(s), comportement actuel, comportement attendu, correction Flutter (code avant/après).

Catégories d'audit (vérifiez les six dans l'ordre) :

  1. Sémantique et lecteur d'écran — étiquettes, rôles, régions en direct, fusion/exclusion de sémantique. Lecteurs d'écran par plateforme : TalkBack (Android), VoiceOver (iOS/macOS), Narrator/NVDA/JAWS (Windows), Orca (Linux)
  2. Tailles de zone tactile — minimum 48x48 dp pour tous les éléments interactifs (critique mobile) ; sur desktop/web, vérifiez que la zone de focus au clavier est visible et suffisante
  3. Focus et navigation au clavier — opérabilité au clavier, ordre de traversal, piégeage du focus de dialogue, indicateurs de focus ; critique pour desktop et web ; vérifiez les alternatives de geste sur mobile
  4. Contraste des couleurs — ratios du texte et du composant UI au seuil du niveau sélectionné (consultez la section Référence des critères de niveau WCAG ci-dessous)
  5. Mise à l'échelle du texte — aucun conteneur de texte à hauteur fixe, aucune mise à l'échelle du texte limitée, gestion du dépassement à l'échelle 200 %
  6. Animation et mouvement — encadrage disableAnimations sur toutes les instances AnimationController, Hero et AnimatedContainer ; pas de contenu clignotant > 3 Hz

Après avoir complété les six catégories, produisez le rapport d'audit en utilisant le modèle spécifique au niveau dans references/audit-templates.md.

Phase 4 — Sélection de l'étendue de la correction

Après avoir livré le rapport, utilisez l'outil AskUserQuestion avec une seule question :

question: "L'audit est terminé. Comment souhaitez-vous procéder aux corrections ?"
header: "Étendue de la correction"
options:
  - label: "Tous les problèmes"
    description: "Corriger toutes les constatations CRITIQUE, MAJEURE et MINEURE"
  - label: "Critique + Majeur uniquement"
    description: "Corriger les bloqueurs et barrières importantes ; ignorer les éléments de finition MINEURE"
  - label: "Critique uniquement"
    description: "Corriger uniquement ce qui bloque entièrement les utilisateurs de technologie d'assistance"
  - label: "Constatations spécifiques"
    description: "Listez les numéros de constatation que vous souhaitez corriger"

Appliquez exactement les corrections que l'utilisateur sélectionne. Après avoir appliqué les corrections, confirmez : « [N] constatations corrigées ([sévérités]). [N restantes] restent ouvertes. »


Référence des critères de niveau WCAG

Utilisez ce tableau lors de la Phase 3 pour déterminer quels critères s'appliquent au niveau sélectionné. Le niveau AA inclut tous les critères du niveau A. Le niveau AAA inclut tous les critères des niveaux A et AA.

Niveau A — Critères fondamentaux

ID WCAG Critère Vérification Flutter
1.1.1 Contenu non textuel semanticLabel sur les images ; Semantics(label:) sur les icônes ; excludeFromSemantics: true sur les images décoratives
1.3.1 Info et relations Rôles sémantiques : button, header, link, checked ; MergeSemantics pour le contenu groupé
1.3.2 Séquence significative L'ordre de lecture correspond à l'ordre visuel ; FocusTraversalGroup avec OrderedTraversalPolicy
1.3.3 Caractéristiques sensorielles Les instructions ne s'appuient pas uniquement sur la forme, la taille, l'emplacement visuel ou le son
1.4.1 Utilisation de la couleur La couleur n'est jamais le seul différenciateur — toujours associée à une icône, une étiquette ou un motif
2.1.1 Clavier Toutes les fonctionnalités via accès au clavier/commutateur ; pas de GestureDetector nu
2.1.2 Pas de piégeage au clavier Le focus peut toujours être déplacé ; les widgets overlay Flutter standard gèrent cela
2.3.1 Trois clignotements ou en dessous du seuil Aucun contenu ne clignote > 3 fois/seconde
2.4.1 Contourner les blocs Mécanisme de navigation de contournement pour les blocs répétés — plateforme web uniquement
2.4.2 Page titrée Chaque écran a un titre significatif en sémantique — plateforme web : balise <title>
2.4.3 Ordre du focus L'ordre tab/focus préserve le sens ; FocusTraversalOrder avec NumericFocusOrder
2.5.3 Étiquette dans le nom Le texte de l'étiquette visible est contenu dans le nom accessible
3.3.1 Identification des erreurs Les erreurs de formulaire sont identifiées en texte, pas en couleur seule
3.3.2 Étiquettes ou instructions Tous les champs de formulaire ont des étiquettes visibles ; InputDecoration(labelText:)
4.1.2 Nom, rôle, valeur Semantics(label:, button: true), Tooltip ; état exposé via les drapeaux checked, selected, enabled
4.1.3 Messages de statut Semantics(liveRegion: true), SemanticsService.announce() pour le statut asynchrone

Niveau AA — Critères supplémentaires (inclut tous les critères du niveau A)

ID WCAG Critère Vérification Flutter
1.3.4 Orientation L'app ne se verrouille pas sur une seule orientation sans raison essentielle ; supprimez les verrous SystemChrome.setPreferredOrientations
1.3.5 Identifier l'objectif de la saisie Les champs de texte utilisent le keyboardType correct et autofillHints
1.4.3 Contraste (Minimum) Texte normal 4.5:1 ; texte volumineux 3:1 sur le fond
1.4.4 Redimensionner le texte Le texte se met à l'échelle à 200 % sans perte de contenu ou de fonctionnalité ; pas de conteneurs de texte à hauteur fixe, pas de TextScaler.noScaling
1.4.5 Images de texte N'utilisez pas d'images de texte pour le texte stylisé — utilisez le widget Text
1.4.10 Reflux Le contenu se reflète à l'équivalent 320 CSS px sans défilement horizontal — utilisez Flexible, Wrap, SingleChildScrollView
1.4.11 Contraste non textuel Les composants UI et les indicateurs de focus ont au moins 3:1 de contraste
1.4.12 Espacement du texte Le contenu n'est pas perdu lorsque l'espacement des lettres/mots/lignes augmente ; évitez overflow: TextOverflow.clip dans les conteneurs fixes
1.4.13 Contenu au survol ou focus Le contenu survol/focalisable est licenciable, survolable et persistant — web/desktop : survols d'info-bulles et menus
2.4.5 Plusieurs moyens Plus d'une façon de localiser un écran (recherche, navigation, plan du site)
2.4.6 Titres et étiquettes Les titres et étiquettes sont descriptifs ; Semantics(header: true) pour les titres de section
2.4.7 Focus visible L'indicateur de focus au clavier est toujours visible
2.4.11 Apparence du focus (Minimum) L'indicateur de focus a 3:1 de contraste et un contour minimum 2px — priorité desktop/web
3.1.2 Langue des parties Les changements de langue dans le contenu sont identifiés programmatiquement — plateforme web : attribut lang
3.2.3 Navigation cohérente La navigation est cohérente sur les écrans
3.2.4 Identification cohérente Les composants ayant la même fonction sont identifiés de manière cohérente
3.3.3 Suggestion d'erreur Lorsqu'une erreur d'entrée est détectée, une correction est suggérée si possible
3.3.4 Prévention des erreurs Les envois avec données légales/financières sont réversibles ou confirmables

Niveau AAA — Critères supplémentaires (inclut tous les critères des niveaux A et AA)

ID WCAG Critère Vérification Flutter
1.4.6 Contraste (Amélioré) Texte normal 7:1 ; texte volumineux 4.5:1 sur le fond
2.1.3 Clavier (Pas d'exception) Toutes les fonctionnalités via clavier sans exception — pas de GestureDetector nulle part
2.2.3 Pas de limite de temps Aucune limite de temps sauf pour les événements en temps réel
2.2.6 Délais d'expiration Les utilisateurs sont avertis des délais d'expiration d'inactivité
2.3.2 Trois clignotements Aucun contenu ne clignote du tout — zéro tolérance, pas seulement en dessous du seuil
2.3.3 Animation à partir des interactions Toute animation de mouvement peut être désactivée — encadrez chaque AnimationController, Hero, AnimatedContainer sur disableAnimations
2.4.8 Localisation Les utilisateurs savent toujours où ils se trouvent dans l'app
2.4.9 Objectif du lien (Lien uniquement) L'objectif du lien est compréhensible à partir du texte du lien seul
2.4.12 Apparence du focus (Amélioré) Indicateur de focus : au moins 2px, enclos du composant, 3:1 de contraste par rapport aux couleurs adjacentes
2.5.5 Taille de la cible (Amélioré) Zones tactiles au moins 44x44 dp (AAA augmente le minimum pratique de la recommandation 48dp)
2.5.6 Mécanismes d'entrée concurrents L'app ne restreint pas l'entrée à une seule modalité
3.2.5 Changement à la demande Les changements de contexte sont initiés uniquement à la demande de l'utilisateur
3.3.5 Aide Une aide contextuelle est disponible
3.3.6 Prévention des erreurs (Tous) Tous les envois sont réversibles ou confirmables

Sémantique et lecteur d'écran

Le widget Semantics de Flutter communique le but du widget aux lecteurs d'écran (TalkBack sur Android, VoiceOver sur iOS et macOS, Narrator/NVDA/JAWS sur Windows, Orca sur Linux).

Anti-motifs — étiquettes sémantiques manquantes ou incorrectes :

// WRONG — semanticLabel vide sur du contenu significatif
Image.asset('assets/warning_icon.png', semanticLabel: '') // n'annonce rien

// WRONG — Pas de semanticLabel sur image informative
Image.asset('assets/chart.png') // lecteur d'écran ignore ou annonce le nom du fichier

Anti-motif — exclusion du contenu significatif :

// WRONG — Masque le contenu actif à la technologie d'assistance
ExcludeSemantics(
  child: ElevatedButton(onPressed: _submit, child: const Text('Submit')),
)

Régions en direct — contenu dynamique qui se met à jour sans interaction doit annoncer les modifications :

Semantics(liveRegion: true, child: Text('$itemCount items in cart'))
SemanticsService.announce('Upload complete', TextDirection.ltr);

Tailles de zone tactile

Tous les éléments interactifs doivent avoir une zone tactile minimale de 48x48 dp (WCAG 2.5.5).

Anti-motif — zone tactile trop petite :

// WRONG — La zone tactile est 24x24, en dessous du minimum 48dp
SizedBox(
  width: 24,
  height: 24,
  child: GestureDetector(
    onTap: _onTap,
    child: const Icon(Icons.close, size: 24),
  ),
)

Utilisez SizedBox(width: 48, height: 48), ConstrainedBox(minWidth: 48, minHeight: 48) ou Padding(padding: EdgeInsets.all(12)) autour des petites icônes.


Focus et navigation au clavier

Chaque widget interactif doit être accessible via clavier et accès par commutateur (WCAG 2.1.1, 2.1.2). Utilisez FocusTraversalGroup + OrderedTraversalPolicy quand l'ordre de tab par défaut ne correspond pas à l'ordre de lecture visuel. showDialog et showModalBottomSheet gèrent automatiquement le piégeage et la restauration du focus.

Anti-motif — gestionnaire de tap non accessible au clavier :

// WRONG — GestureDetector n'est pas accessible au clavier
GestureDetector(onTap: _onTap, child: const Text('Click me'))

// CORRECT — InkWell est focalisable et accessible au clavier
InkWell(onTap: _onTap, child: const Text('Click me'))

Les indicateurs de focus personnalisés doivent respecter 3:1 de contraste (WCAG 2.4.11 AA) ; à AAA (2.4.12), l'indicateur doit être ≥ 2px et enclore le composant.


Contraste des couleurs

Les exigences de contraste commencent au niveau AA — le niveau A n'a pas d'exigence de contraste.

Élément Niveau AA Niveau AAA Critère WCAG
Texte normal (< 18pt / < 14pt gras) 4.5:1 7:1 1.4.3 / 1.4.6
Texte volumineux (>= 18pt / >= 14pt gras) 3:1 4.5:1 1.4.3 / 1.4.6
Composants UI et indicateurs de focus 3:1 3:1 1.4.11

Utilisez les jetons Theme.of(context).colorScheme pour un contraste cohérent.

Anti-motifs :

// WRONG — Gris clair sur blanc échoue 4.5:1
Text('Status: Active', style: TextStyle(color: Colors.grey.shade300))

// WRONG — Couleur comme seul différenciateur
Container(color: isValid ? Colors.green : Colors.red) // pas d'étiquette ou d'icône

Mise à l'échelle du texte

Les widgets doivent s'adapter aux préférences de taille de police de l'utilisateur jusqu'à l'échelle 2x sans découpe (WCAG 1.4.4).

Anti-motif — hauteur fixe autour du texte :

// WRONG — La hauteur fixe découpe le texte à l'échelle de police 1,5x
SizedBox(height: 48, child: Text('This text will be clipped at 1.5x font scale'))

Anti-motif — limitation de la mise à l'échelle du texte :

// WRONG — Remplace les préférences d'accessibilité de l'utilisateur
MediaQuery(
  data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
  child: const Text('Ignoring user font preferences'),
)

Utilisez ConstrainedBox(constraints: BoxConstraints(minHeight: 48)) autour des conteneurs de texte.


Animation et mouvement

Toutes les animations doivent respecter MediaQuery.disableAnimations (WCAG 2.3.3). Aucun contenu ne peut clignoter > 3 Hz (WCAG 2.3.1).

Anti-motif — ignorer la préférence de mouvement réduit :

// WRONG — L'animation est toujours lue indépendamment de la préférence utilisateur
AnimatedContainer(
  duration: const Duration(milliseconds: 500),
  color: _isActive ? Colors.blue : Colors.grey,
  child: child,
)

// CORRECT — Encadrer sur disableAnimations
AnimatedContainer(
  duration: MediaQuery.of(context).disableAnimations
      ? Duration.zero
      : const Duration(milliseconds: 500),
  color: _isActive ? Colors.blue : Colors.grey,
  child: child,
)

Ressources supplémentaires

Références officielles :

Skills similaires