L'accessibilité est un de ces sujets où l'écart entre ce que les équipes disent et ce qu'elles livrent est gênant. J'ai été des deux côtés de cet écart. Au début de ma carrière j'ai livré des interfaces qui échouaient aux vérifications de contraste basiques. Au fil du temps, en dirigeant la refonte au Strongest Families Institute puis en livrant des fonctionnalités conformes en accessibilité chez BMO, j'ai fait de l'accessibilité une partie non négociable de mon propre travail, dans les designs que je transmets comme dans le code que j'écris. Cet article est le minimum distillé que je m'impose et que je pousse dans chaque équipe que je rejoins.
Pourquoi WCAG 2.2 AA est votre base, pas votre plafond
WCAG signifie Web Content Accessibility Guidelines, et la base à viser est le niveau AA de la version 2.2. Elle est devenue une recommandation du W3C en octobre 2023 et remplace la 2.1, qui est maintenant la cible héritée. Le niveau A couvre les échecs les plus critiques. Le niveau AAA est aspirationnel et souvent impraticable pour les applications complexes. AA se situe au milieu : réalisable avec de la discipline, significatif pour les vrais utilisateurs, et légalement pertinent au Canada. Les organisations sous réglementation fédérale comme les banques relèvent de la Loi canadienne sur l'accessibilité et de la Loi canadienne sur les droits de la personne. L'Ontario ajoute la loi ontarienne sur l'accessibilité (AODA), dont le règlement sur l'accessibilité pointe encore vers WCAG 2.0 AA, et les autres provinces ont leurs propres lois. Un employeur sous réglementation fédérale qui opère en Ontario peut relever de plusieurs de ces textes à la fois. Construire au niveau 2.2 AA les couvre tous.
Si votre projet a été cadré sous la 2.1, l'écart est petit et vaut la peine d'être comblé. WCAG 2.2 ajoute quatre critères AA : 2.4.11 Focus Not Obscured (Minimum), 2.5.7 Dragging Movements, 2.5.8 Target Size (Minimum) à 24x24 pixels CSS, et 3.3.8 Accessible Authentication (Minimum). Elle retire aussi 4.1.1 Parsing, qui ne s'applique plus. Faites une revue d'écart sur ces cinq points et vous êtes à jour.
Dans le secteur bancaire, la conformité AA n'est pas optionnelle. La Loi canadienne sur l'accessibilité impose aux organisations sous réglementation fédérale l'obligation de repérer, d'éliminer et de prévenir les obstacles, alors l'accessibilité est traitée comme une catégorie de risque au même titre que la sécurité et la confidentialité des données, et les fonctionnalités que j'ai construites chez BMO devaient être conformes en accessibilité. Ce cadrage est utile partout : quand on arrête de présenter l'accessibilité comme un bien moral et qu'on commence à la présenter comme de l'atténuation de risque, la conversation avec le business devient beaucoup plus courte.
Contraste de couleurs : l'échec le plus courant
WCAG AA exige un ratio de contraste d'au moins 4.5:1 pour le texte normal et 3:1 pour le grand texte (18pt régulier ou 14pt gras). Ces ratios semblent techniques mais la règle est simple : si votre texte gris sur blanc semble subtil et élégant, il échoue probablement.
Deux outils valent la peine : le WebAIM Contrast Checker pour tout ce dans quoi vous pouvez coller une paire de codes hexadécimaux, et un plugin de vérification de contraste dans Figma pour que la vérification se fasse avant le handoff. Dans Figma, je fais des vérifications de contraste avant que tout design n'aille en dev. Chez Strongest Families, j'ai mis en place le design system pour la refonte de la plateforme IRIS, et le contraste a été l'une des premières contraintes intégrées dedans : chaque paire de couleurs était vérifiée contre le AA avant de rejoindre la palette, ce qui a aussi gardé la palette réduite. Un bénéfice secondaire qui a amélioré la cohérence du produit.
- Texte normal (sous 18pt régulier / 14pt gras) : ratio minimum 4.5:1
- Grand texte : ratio minimum 3:1
- Composants UI et objets graphiques : minimum 3:1 contre les couleurs adjacentes
- Éléments décoratifs sans valeur informationnelle : pas d'exigence
- Texte de placeholder dans les inputs : doit aussi atteindre 4.5:1, souvent oublié
- Éléments en état désactivé : explicitement exclus des exigences de contraste, mais à utiliser avec parcimonie
Navigation clavier : chaque élément interactif
Chaque élément interactif dans votre UI doit être atteignable et opérable en utilisant uniquement un clavier. Ça signifie les liens, boutons, champs de formulaire, modales, menus déroulants, sélecteurs de date, composants select personnalisés : tout. L'ordre de tabulation doit être logique et correspondre à l'ordre de lecture visuel.
Le mode d'échec que je vois le plus souvent dans les applications Angular est les composants personnalisés qui avalent les événements clavier. Un développeur construit un magnifique dropdown avec des divs et des click handlers. Ça fonctionne parfaitement avec une souris. Au clavier c'est complètement invisible. Je traite la navigation clavier comme faisant partie des critères d'acceptation de chaque composant interactif que je construis, que le ticket le précise ou non. Si ce n'est pas dans les CA, ça ne se teste pas, et si ça ne se teste pas, ça ne se livre pas bien.
// Bad: div-based button with no keyboard support
// <div class="btn" (click)="submit()">Submit</div>
// Good: native button or explicit keyboard handler
// <button type="button" (click)="submit()">Submit</button>
// If you must use a non-semantic element:
@Component({
selector: 'app-custom-button',
template: `
<div
class="custom-btn"
role="button"
[attr.tabindex]="disabled ? -1 : 0"
[attr.aria-disabled]="disabled"
(click)="handleAction()"
(keydown.enter)="handleAction()"
(keydown.space)="handleAction()">
<ng-content></ng-content>
</div>
`
})
export class CustomButtonComponent {
@Input() disabled = false;
handleAction(): void {
if (!this.disabled) {
// emit action
}
}
}Ma préférence forte est toujours d'utiliser les éléments HTML natifs : button, a, input, select. Ils viennent avec le support clavier, la gestion du focus et la sémantique ARIA intégrés. Chaque composant personnalisé que vous construisez pour remplacer un natif est une dette que vous devrez payer en attributs ARIA et gestionnaires d'événements.
Gestion du focus : la partie que tout le monde oublie
La navigation clavier concerne l'atteinte des éléments. La gestion du focus concerne où va le focus après une action. Cette distinction compte énormément dans les applications monopages.
Prenons une boîte de dialogue modale. Un utilisateur appuie sur Entrée sur un bouton déclencheur. La modale s'ouvre. Où est le focus ? Si vous ne le gérez pas, le focus reste sur le bouton déclencheur derrière la modale. L'utilisateur tabule vers l'avant et atterrit sur des éléments en arrière-plan qui sont maintenant visuellement cachés. C'est un échec du critère de succès 2.4.3 Ordre du focus, qui exige que l'ordre du focus préserve le sens et l'utilisabilité. Et c'est aussi une expérience réellement terrible pour les utilisateurs de lecteur d'écran.
- Quand une modale s'ouvre : déplacer le focus vers le conteneur de la modale ou son premier élément interactif
- Quand une modale se ferme : retourner le focus à l'élément qui l'a déclenchée
- Quand on navigue entre les routes : déplacer le focus vers le titre du contenu principal ou le conteneur de page
- Quand on affiche des erreurs inline après soumission de formulaire : déplacer le focus vers la première erreur ou vers un résumé
- Quand on développe un accordéon : le focus peut rester sur le déclencheur, ne le déplacer que si du contenu est chargé dynamiquement
- Piéger le focus dans les modales : l'utilisateur ne devrait pas pouvoir tabuler hors d'une modale ouverte vers le contenu de fond
Avant d'écrire tout ça vous-même, regardez @angular/cdk/a11y. Il fournit cdkTrapFocus, FocusTrapFactory, FocusMonitor et LiveAnnouncer, et sur un projet Angular vous avez déjà le CDK sur disque. N'écrivez un piège de focus à la main que si vous avez besoin d'un comportement que la directive ne couvre pas. Dans tous les cas, notez la garde sur la liste vide ci-dessous. Si le conteneur n'a rien de focusable, first et last sont undefined et appeler focus() dessus lève une erreur.
// Focus trap service for modals in Angular
import { Injectable, ElementRef } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class FocusTrapService {
private focusableSelectors = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])'
].join(', ');
getFocusableElements(container: HTMLElement): HTMLElement[] {
return Array.from(
container.querySelectorAll<HTMLElement>(this.focusableSelectors)
).filter(el => !el.closest('[hidden]'));
}
trapFocus(container: HTMLElement, event: KeyboardEvent): void {
if (event.key !== 'Tab') {
return;
}
const focusable = this.getFocusableElements(container);
if (focusable.length === 0) {
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
}Labels ARIA : les utiliser correctement ou pas du tout
ARIA, Accessible Rich Internet Applications, est un ensemble d'attributs HTML qui communiquent du sens sémantique aux technologies d'assistance. La règle la plus importante sur ARIA est la première règle d'ARIA : n'utilisez pas ARIA s'il existe un élément HTML natif qui fait le travail. role='button' sur un div est toujours pire qu'un élément button.
Ceci dit, il existe des cas légitimes où ARIA est nécessaire : widgets complexes, contenu dynamique, composants personnalisés. Voici les attributs que j'utilise le plus fréquemment et ce que chacun fait.
- aria-label : fournit un label texte pour un élément quand le texte visible est absent ou insuffisant, utiliser sur les boutons icône-seule
- aria-labelledby : pointe vers l'ID d'un autre élément dont le texte sert de label, utiliser pour les fieldsets de formulaire et les dialogues
- aria-describedby : pointe vers du texte descriptif additionnel, utiliser pour le texte d'aide d'input et les messages d'erreur
- aria-expanded : communique l'état ouvert/fermé des accordéons, dropdowns et menus
- aria-live : marque une région qui recevra des mises à jour dynamiques, les lecteurs d'écran annonceront les changements automatiquement
- aria-hidden : cache les éléments décoratifs de l'arbre d'accessibilité, utiliser sur les icônes dans les boutons labellisés
- role : remplace le rôle ARIA implicite, utiliser avec parcimonie, uniquement quand l'élément sémantique n'est vraiment pas disponible
// Practical ARIA in Angular templates
// Icon-only button
<button type="button" aria-label="Close notification">
<app-icon name="close" aria-hidden="true"></app-icon>
</button>
// Input with visible label and helper text
<div class="field">
<label id="email-label" for="email">Email address</label>
<input
id="email"
type="email"
aria-labelledby="email-label"
aria-describedby="email-hint email-error"
[attr.aria-invalid]="emailControl.invalid && emailControl.touched" />
<span id="email-hint">We will never share your email.</span>
<span id="email-error" role="alert" *ngIf="emailControl.invalid && emailControl.touched">
Please enter a valid email address.
</span>
</div>
// Expandable panel
<button
[id]="'btn-' + id"
[attr.aria-expanded]="isOpen"
[attr.aria-controls]="'panel-' + id"
(click)="toggle()">
{{ title }}
</button>
<div
[id]="'panel-' + id"
[hidden]="!isOpen"
role="region"
[attr.aria-labelledby]="'btn-' + id">
<ng-content></ng-content>
</div>Tests de lecteur d'écran : vous ne pouvez pas sauter cette étape
Les outils automatisés comme Axe, Lighthouse et IBM Equal Access Checker attrapent beaucoup de choses, mais pas tout. L'étude de Deque en 2021 sur ses propres audits a trouvé que les tests automatisés axe attrapaient environ 57 % des problèmes en volume (le chiffre plus ancien de 20 à 30 % désigne la couverture des critères de succès WCAG, pas le volume de problèmes). Le reste demande des tests manuels. Le test manuel le plus précieux est le test de lecteur d'écran. Éteindre votre moniteur et essayer de compléter une tâche en utilisant uniquement l'audio.
Ma configuration de test utilise NVDA sur Windows avec Firefox et VoiceOver sur macOS avec Safari. Les deux figurent parmi les cinq combinaisons les plus courantes du dernier sondage sur les lecteurs d'écran publié par WebAIM, même si NVDA est maintenant utilisé avec Chrome environ deux fois plus souvent qu'avec Firefox. Avant qu'une de mes fonctionnalités ne soit livrée, je fais un parcours au lecteur d'écran. Ce n'est pas un audit complet, mais ça attrape les échecs évidents avant qu'ils n'atteignent la production.
- NVDA + Firefox (Windows) : gratuit, largement utilisé, essentiel pour les audiences d'entreprise sous Windows
- VoiceOver + Safari (macOS/iOS) : intégré aux appareils Apple, critique pour le mobile
- TalkBack + Chrome (Android) : essentiel pour les tests mobiles Android
- JAWS + Chrome ou Edge (Windows) : lecteur d'écran d'entreprise coûteux courant en banque et gouvernement
Un exemple typique : un formulaire multi-étapes affiche 'Étape 1 sur 5' visuellement, mais VoiceOver ne lit que 'Formulaire.' L'indicateur de progression est construit entièrement avec des classes CSS, sans markup sémantique. Mettre un aria-label sur un conteneur ordinaire ne règle pas le problème, parce que les lecteurs d'écran n'annoncent pas de façon fiable les labels sur des éléments génériques. Ce qui fonctionne, c'est un texte visuellement caché qui annonce l'étape en cours, plus aria-current='step' sur l'élément actif de la liste d'étapes. C'est le genre de problème qu'aucun outil automatisé n'attrapera.
Ce que je vérifie à chaque étape
Je vérifie l'accessibilité à trois étapes : revue de design avant le handoff au dev, revue de composant pendant le développement, et une passe pré-release. Chez Strongest Families, je possédais les trois, parce que je designais et je transmettais. Chez BMO j'étais côté développement, avec le design system de la banque, qui appartenait à une autre équipe, donc les étapes que j'appliquais étaient la revue de composant et la passe pré-release sur mes propres fonctionnalités. La checklist de l'étape design couvre les points suivants.
- Tous les ratios de contraste de texte et d'éléments interactifs vérifiés et documentés dans Figma
- États de focus visibles et stylés pour chaque élément interactif, pas juste le contour par défaut du navigateur
- Les états d'erreur incluent à la fois couleur et icône ou texte, jamais la couleur seule
- Les champs de formulaire ont des labels visibles. Le texte placeholder seul échoue au critère 3.3.2 Étiquettes ou instructions, et au 1.3.1 également quand il n'y a aucun label programmatique
- Tout contenu transmis par la couleur seule a un indicateur secondaire
- Les cibles tactiles respectent le critère 2.5.8 Target Size (Minimum), 24x24 pixels CSS, qui est la règle AA. Sur mobile je conçois à 44x44, ce qui correspond au critère 2.5.5 au niveau AAA et aux recommandations des plateformes Apple et Material
- Les modales et dialogues ont un mécanisme de fermeture explicite et un titre
- Les images ont un texte alternatif spécifié dans le design, les images décoratives sont explicitement marquées comme décoratives
- Les tableaux de données ont les en-têtes de colonnes et de lignes identifiés
- Le mouvement et l'animation ont une considération de mouvement réduit
L'argumentaire business est plus facile que vous ne le pensez
Chaque fois que j'ai dû plaider pour un investissement en accessibilité, j'ai utilisé les mêmes trois arguments. Premièrement, le risque juridique : au Canada et dans la plupart des juridictions occidentales, ne pas fournir des services numériques accessibles aux clients d'une banque est actionnable en justice. Deuxièmement, la taille du marché : environ 16 % de la population mondiale, soit 1,3 milliard de personnes ou 1 sur 6, vit avec un handicap important selon l'OMS, et le design accessible améliore l'utilisabilité pour tous les utilisateurs, pas seulement ceux avec des handicaps. Troisièmement, l'effet rampe de trottoir : les fonctionnalités construites pour l'accessibilité, comme les sous-titres, la commande vocale et les raccourcis clavier, sont utilisées massivement par des personnes sans handicap.
L'accessibilité n'est pas une fonctionnalité qu'on ajoute à la fin. C'est un attribut de qualité qu'on intègre dès le départ, comme la performance ou la sécurité. Si vous la retrouvez en retard, vous payez déjà la pénalité.
Le minimum que vous devez connaître n'est pas si conséquent. Apprenez les quatre principes WCAG : Perceptible, Utilisable, Compréhensible, Robuste. Passez vos designs dans un vérificateur de contraste avant le handoff. Testez au clavier avant chaque release. Testez avec un lecteur d'écran avant chaque lancement de fonctionnalité majeure. Ajoutez ARIA uniquement là où le HTML natif est insuffisant. C'est le minimum. Commencez là et construisez vers le haut.
Références
- WCAG 2.2. https://www.w3.org/TR/WCAG22/
- WebAIM Contrast Checker. https://webaim.org/resources/contrastchecker/
- NVDA screen reader. https://www.nvaccess.org/download/
- Angular CDK a11y module. https://material.angular.dev/cdk/a11y
- AODA (Ontario). https://www.ontario.ca/laws/statute/05a11
- Accessible Canada Act (S.C. 2019, c. 10). https://laws-lois.justice.gc.ca/eng/acts/A-0.6/
- World Health Organization. Disability fact sheet. https://www.who.int/news-room/fact-sheets/detail/disability-and-health
- WCAG 2.2 Understanding SC 2.5.8 Target Size (Minimum). https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html