Accessibilité (a11y)
Directives complètes d'accessibilité basées sur WCAG 2.2 et les audits d'accessibilité Lighthouse. Objectif : rendre le contenu utilisable par tous, y compris les personnes handicapées.
Flux de travail d'audit basé sur les preuves
Lorsqu'une page rendue est disponible :
- Exécuter un audit d'accessibilité Lighthouse en direct lorsque cette capacité est disponible ; avec Chrome DevTools MCP, utilisez
lighthouse_audit. Utilisez le mode de navigation mobile pour une page publique générale ou le mode instantané lorsque le rechargement perdrait l'état authentifié ou créé par l'utilisateur. - Utilisez les nœuds d'audit défaillants pour localiser le composant ou le modèle pertinent au lieu de rechercher dans tout le dépôt des modèles génériques.
- Inspectez un instantané de l'arbre d'accessibilité rendu pour les noms, rôles, états, points de repère et la structure des en-têtes ; avec Chrome DevTools MCP, utilisez
take_snapshot. Parcourez le flux concerné avec le clavier. - Corrigez la source, puis réexécutez le même audit et l'interaction manuelle.
Si les outils en direct ne sont pas disponibles, utilisez Lighthouse CLI ou axe pour une couverture automatisée et effectuez les mêmes vérifications manuelles. Les outils automatisés ne détectent qu'un sous-ensemble des barrières d'accessibilité : un score de 100 n'est pas une conformité WCAG, et un score faible ne remplace pas les preuves au niveau des problèmes.
Principes WCAG : POUR
| Principe | Description |
|---|---|
| Perceptible | Le contenu peut être perçu par différents sens |
| Opérable | L'interface peut être utilisée par tous les utilisateurs |
| Understandable (Compréhensible) | Le contenu et l'interface sont compréhensibles |
| Robuste | Le contenu fonctionne avec les technologies d'assistance |
Niveaux de conformité
| Niveau | Exigence | Cible |
|---|---|---|
| A | Accessibilité minimale | Doit être réussi |
| AA | Conformité standard | Devrait être réussi (exigence légale dans de nombreuses juridictions) |
| AAA | Accessibilité renforcée | Agréable à avoir |
Perceptible
Alternatives textuelles (1.1)
Les images nécessitent un texte alternatif :
<!-- ❌ Texte alternatif manquant -->
<img src="chart.png">
<!-- ✅ Texte alternatif descriptif -->
<img src="chart.png" alt="Diagramme à barres montrant une augmentation de 40% des ventes au T3">
<!-- ✅ Image décorative (texte alternatif vide) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ Image complexe avec description longue -->
<figure>
<img src="infographic.png" alt="Infographie des tendances du marché 2024"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- Description détaillée -->
</figcaption>
</figure>
Les boutons d'icônes ont besoin de noms accessibles :
<!-- ❌ Pas de nom accessible -->
<button><svg><!-- icône menu --></svg></button>
<!-- ✅ Utilisation d'aria-label -->
<button aria-label="Ouvrir le menu">
<svg aria-hidden="true"><!-- icône menu --></svg>
</button>
<!-- ✅ Utilisation d'un texte masqué visuellement -->
<button>
<svg aria-hidden="true"><!-- icône menu --></svg>
<span class="visually-hidden">Ouvrir le menu</span>
</button>
Classe de masquage visuel :
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Contraste des couleurs (1.4.3, 1.4.6)
| Taille du texte | AA minimum | AAA renforcé |
|---|---|---|
| Texte normal (< 18px / < 14px gras) | 4,5:1 | 7:1 |
| Grand texte (≥ 18px / ≥ 14px gras) | 3:1 | 4,5:1 |
| Composants d'interface et graphiques | 3:1 | 3:1 |
/* ❌ Faible contraste (2,5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ Contraste suffisant (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ Les états de focus ont aussi besoin de contraste (3:1 par rapport au fond, WCAG 1.4.11) */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Ne pas se fier uniquement à la couleur :
<!-- ❌ Seule la couleur indique une erreur -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ Couleur + icône + texte -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- icône d'erreur --></svg>
Veuillez saisir une adresse e-mail valide
</span>
</div>
Alternatives médiatiques (1.2)
<!-- Vidéo avec sous-titres -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="fr" label="Français" default>
<track kind="descriptions" src="descriptions.vtt" srclang="fr" label="Descriptions">
</video>
<!-- Audio avec transcription -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>Transcription</summary>
<p>Texte complet de la transcription...</p>
</details>
Opérable
Accessible au clavier (2.1)
Toutes les fonctionnalités doivent être accessibles au clavier. Privilégiez les éléments interactifs natifs — <button>, <a href>, et les contrôles de formulaire gèrent l'activation par Entrée/Espace, le focus et la sémantique pour les technologies d'assistance gratuitement. N'ajoutez une gestion manuelle du clavier que lorsque vous ne pouvez pas utiliser un élément natif.
<!-- ❌ Élément non interactif avec uniquement un clic : pas focalisable, pas d'activation au clavier -->
<div class="card" onclick="handleAction()">Ouvrir</div>
<!-- ✅ Meilleur : utiliser un bouton natif -->
<button type="button" onclick="handleAction()">Ouvrir</button>
// ✅ Lorsque vous DEVEZ utiliser un élément non interactif (par ex. div avec role="button"),
// rendez-le focalisable ET gérez l'activation au clavier. Ne l'ajoutez PAS à un <button>
// natif — Entrée/Espace déclenchent déjà le clic, vous auriez donc un double déclenchement.
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
Pas de pièges clavier. Les utilisateurs doivent pouvoir naviguer par Tab pour entrer et sortir de chaque composant. Utilisez le modèle de piège de focus modal pour les boîtes de dialogue — l'élément natif <dialog> gère cela automatiquement.
Focus visible (2.4.7)
/* ❌ Ne jamais supprimer les contours de focus */
*:focus { outline: none; }
/* ✅ Utiliser :focus-visible pour le focus uniquement au clavier */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor; /* hérite de la couleur du texte → déjà vérifié pour le contraste */
outline-offset: 2px;
}
/* ✅ Ou choisir une couleur de marque et vérifier un contraste ≥3:1 par rapport à chaque fond */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
Focus non masqué (2.4.11) — nouveau en 2.2
Lorsqu'un élément reçoit le focus clavier, il ne doit pas être entièrement caché par d'autres contenus créés par l'auteur, tels que les en-têtes collants, les pieds de page ou les panneaux superposés. Au niveau AAA (2.4.12), aucune partie de l'élément focalisé ne peut être masquée.
/* ✅ Tenir compte des en-têtes collants lors du défilement vers les éléments focalisés */
:target {
scroll-margin-top: 80px;
}
/* ✅ S'assurer que les éléments focalisés dépassent les barres fixes/collantes */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
Liens d'évitement (2.4.1)
Fournissez un lien d'évitement afin que les utilisateurs du clavier puissent contourner la navigation répétitive. Voir le modèle de lien d'évitement pour le balisage et les styles complets.
Taille des cibles (2.5.8) — nouveau en 2.2
Les cibles interactives doivent mesurer au moins 24 × 24 pixels CSS (AA). Exceptions : les liens de texte en ligne, les éléments dont la taille est contrôlée par le navigateur, et les cibles où un cercle de 24 px centré sur la boîte englobante ne chevauche pas une autre cible.
/* ✅ Taille minimale des cibles */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ Taille confortable des cibles (recommandé 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
Mouvements de glissement (2.5.7) — nouveau en 2.2
Toute action nécessitant un glissement doit avoir une alternative à pointeur unique (par exemple, boutons, champs de saisie). Voir le modèle de mouvements de glissement pour un exemple de liste triable.
Temporisation (2.2)
// Permettre aux utilisateurs de prolonger les délais
function showSessionWarning() {
const modal = createModal({
title: 'Session en cours d\'expiration',
content: 'Votre session expirera dans 2 minutes.',
actions: [
{ label: 'Prolonger la session', action: extendSession },
{ label: 'Se déconnecter', action: logout }
],
timeout: 120000
});
}
Mouvement (2.3)
/* Respecter la préférence de mouvement réduit */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Compréhensible
Langue de la page (3.1.1)
<!-- ❌ Aucune langue spécifiée -->
<html>
<!-- ✅ Langue spécifiée -->
<html lang="fr">
<!-- ✅ Changements de langue dans la page -->
<p>Le mot français pour dire bonjour est <span lang="en">hello</span>.</p>
Navigation cohérente (3.2.3)
<!-- La navigation doit être cohérente entre les pages -->
<nav aria-label="Principal">
<ul>
<li><a href="/" aria-current="page">Accueil</a></li>
<li><a href="/produits">Produits</a></li>
<li><a href="/a-propos">À propos</a></li>
</ul>
</nav>
Aide cohérente (3.2.6) — nouveau en 2.2
Si un mécanisme d'aide (informations de contact, widget de chat, lien FAQ, option d'auto-assistance) est répété sur plusieurs pages, il doit apparaître dans le même ordre relatif à chaque fois. Les utilisateurs qui comptent sur un emplacement cohérent ne doivent pas avoir à chercher de l'aide sur chaque page.
Étiquettes de formulaire (3.3.2)
Chaque champ de saisie a besoin d'une étiquette associée par programme. Voir le modèle d'étiquettes de formulaire pour des exemples explicites, implicites et instructifs.
Gestion des erreurs (3.3.1, 3.3.3)
Annoncez les erreurs aux lecteurs d'écran avec role="alert" ou aria-live, définissez aria-invalid="true" sur les champs non valides, et concentrez la première erreur lors de la soumission. Voir le modèle de gestion des erreurs pour le balisage et le JS complets.
Saisie redondante (3.3.7) — nouveau en 2.2
N'obligez pas les utilisateurs à ressaisir les informations qu'ils ont déjà fournies dans la même session. Remplissez automatiquement à partir des étapes précédentes, ou laissez les utilisateurs sélectionner parmi les valeurs déjà saisies. Exceptions : reconfirmation de sécurité et contenu qui a expiré.
<!-- ✅ Remplir automatiquement l'adresse de livraison à partir de la facturation -->
<fieldset>
<legend>Adresse de livraison</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
Identique à l'adresse de facturation
</label>
<!-- Champs automatiquement remplis lorsque la case est cochée -->
</fieldset>
Authentification accessible (3.3.8) — nouveau en 2.2
Les flux de connexion ne doivent pas reposer sur des tests de fonction cognitive (par exemple, se souvenir d'un mot de passe, résoudre un puzzle) à moins qu'au moins l'une des conditions suivantes ne soit remplie :
- Un mécanisme de copier-coller ou de remplissage automatique est disponible
- Une méthode alternative existe (par exemple, clé d'accès, SSO, lien par e-mail)
- Le test utilise la reconnaissance d'objets ou du contenu personnel (AA uniquement ; AAA supprime cette exception)
<!-- ✅ Autoriser le collage dans les champs de mot de passe -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ Proposer des alternatives sans mot de passe -->
<button type="button">Se connecter avec une clé d'accès</button>
<button type="button">Envoyer un lien de connexion par e-mail</button>
Robuste
Utilisation d'ARIA (4.1.2)
Privilégiez les éléments natifs :
<!-- ❌ Rôle ARIA sur un div -->
<div role="button" tabindex="0">Cliquez-moi</div>
<!-- ✅ Bouton natif -->
<button>Cliquez-moi</button>
<!-- ❌ Case à cocher ARIA -->
<div role="checkbox" aria-checked="false">Option</div>
<!-- ✅ Case à cocher native -->
<label><input type="checkbox"> Option</label>
Lorsque ARIA est nécessaire, utilisez les rôles et états appropriés. Voir le modèle d'onglets ARIA pour un exemple complet de liste d'onglets.
Régions dynamiques (4.1.3)
Utilisez les régions aria-live pour annoncer les changements de contenu dynamique sans déplacer le focus. Voir le modèle de régions dynamiques pour le balisage et un helper showNotification().
Liste de vérification des tests
Tests automatisés
Préférez un audit Lighthouse en direct qui renvoie les nœuds rendus défaillants directement à l'agent. Avec Chrome DevTools MCP, c'est lighthouse_audit. Sinon :
# Audit d'accessibilité Lighthouse
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
Tests manuels
- Navigation au clavier : Parcourir toute la page avec la touche Tab, utiliser Entrée/Espace pour activer
- Lecteur d'écran : Tester avec VoiceOver (Mac), NVDA (Windows) ou TalkBack (Android)
- Zoom : Contenu utilisable à 200% de zoom
- Contraste élevé : Tester avec le mode Contraste élevé de Windows
- Mouvement réduit : Tester avec
prefers-reduced-motion: reduce - Ordre de focus : Logique et suit l'ordre visuel
- Taille des cibles : Les éléments interactifs respectent le minimum de 24×24 px
Voir la référence des commandes du lecteur d'écran pour les raccourcis VoiceOver et NVDA.
Problèmes courants par impact
Critique (corriger immédiatement)
- Étiquettes de formulaire manquantes
- Texte alternatif d'image manquant
- Contraste de couleur insuffisant
- Pièges clavier
- Indicateurs de focus manquants
Grave (corriger avant le lancement)
- Langue de la page manquante
- Structure d'en-têtes manquante
- Texte de lien non descriptif
- Média à lecture automatique
- Liens d'évitement manquants
Modéré (corriger bientôt)
- Étiquettes ARIA manquantes sur les icônes
- Navigation incohérente
- Identification d'erreur manquante
- Temporisation sans contrôles
- Régions de repère manquantes


