Que fait Expert en Automatisation de l'Accessibilité ?
Implémentez et appliquez la conformité WCAG 2.2 AA/AAA via des tests automatisés, la navigation au clavier, les motifs ARIA, l'optimisation pour les lecteurs d'écran et la gestion du focus.
Déclencheurs d'activation
Activer sur : échecs d'audit d'accessibilité, exigences de conformité WCAG, navigation clavier cassée, contenu non annoncé par le lecteur d'écran, violations axe-core, piège de focus pour modales/dialogs, questions sur les attributs aria-*, liens de navigation de saut.
PAS pour : revue d'accessibilité au niveau design (choix de couleurs, décisions de mise en page) — utilisez design-accessibility-auditor. Vérification pure du contraste des couleurs — utilisez css-in-js-architect avec OKLCH.
Démarrage rapide
- Exécutez axe-core —
npx @axe-core/cli http://localhost:3000ou intégrez@axe-core/reacten mode développement pour des avertissements dans la console. - Corrigez d'abord les violations critiques — texte alternatif manquant, étiquettes de formulaire manquantes, contraste insuffisant, points de repère manquants.
- Implémentez la navigation au clavier — chaque élément interactif doit être atteignable et utilisable avec les touches Tab, Entrée, Espace, Échap et les flèches.
- Ajoutez la gestion du focus — piégez le focus dans les modales, restaurez le focus à la fermeture, gérez le focus lors des changements de route.
- Testez avec un lecteur d'écran — VoiceOver (macOS), NVDA (Windows) ou TalkBack (Android) pour une validation réelle.
Capacités principales
| Domaine | Technologies | Motifs clés |
|---|---|---|
| Tests automatisés | axe-core, Lighthouse, jest-axe, Playwright axe | Portes CI/CD d'accessibilité |
| Navigation clavier | tabindex, onKeyDown, tabindex tournant | Navigation par flèches, groupes de focus |
| Lecteurs d'écran | Rôles ARIA, régions live, aria-label | Annonces, changements d'état, descriptions |
| Gestion du focus | focus-visible, piège de focus, attribut inert | Verrouillage du focus de la modale, liens de saut, focus sur changement de route |
| HTML sémantique | <main>, <nav>, <article>, <aside> | Points de repère, hiérarchie des titres, listes |
| Formulaires | <label>, aria-describedby, aria-invalid | Annonce d'erreur, champs obligatoires, fieldsets |
Schémas d'architecture
Schéma 1 : axe-core automatisé dans CI
// e2e/accessibility.spec.ts (Playwright + axe-core)
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const pages = ['/', '/products', '/checkout', '/account'];
for (const path of pages) {
test(`${path} n'a aucune violation d'accessibilité`, async ({ page }) => {
await page.goto(path);
await page.waitForLoadState('networkidle');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa']) // WCAG 2.2 AA
.analyze();
expect(results.violations).toEqual([]);
});
}
// Mode développement : axe-core dans React (affiche les violations dans la console)
// app/layout.tsx
if (process.env.NODE_ENV === 'development') {
import('@axe-core/react').then((axe) => {
axe.default(React, ReactDOM, 1000);
});
}
Schéma 2 : Gestion du focus pour les modales
import { useEffect, useRef, useCallback } from 'react';
function useFocusTrap(isOpen: boolean) {
const containerRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!isOpen) return;
// Sauvegarder le focus actuel pour le restaurer plus tard
previousFocusRef.current = document.activeElement as HTMLElement;
// Focus sur le premier élément focusable
const container = containerRef.current;
if (!container) return;
const focusable = container.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
focusable[0]?.focus();
// Piéger le focus dans le conteneur
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last?.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first?.focus();
}
};
container.addEventListener('keydown', handleKeyDown);
// Utiliser inert sur le contenu de fond
const mainContent = document.querySelector('main');
mainContent?.setAttribute('inert', '');
return () => {
container.removeEventListener('keydown', handleKeyDown);
mainContent?.removeAttribute('inert');
previousFocusRef.current?.focus(); // restaurer le focus
};
}, [isOpen]);
return containerRef;
}
Schéma 3 : Régions Live ARIA pour le contenu dynamique
// Annoncer les erreurs de formulaire, les états de chargement et les mises à jour aux lecteurs d'écran
function useAnnounce() {
const announce = useCallback((message: string, priority: 'polite' | 'assertive' = 'polite') => {
const el = document.getElementById(`aria-live-${priority}`);
if (el) {
el.textContent = ''; // Effacer d'abord pour déclencher une ré-annonce
requestAnimationFrame(() => { el.textContent = message; });
}
}, []);
return announce;
}
// Monter une fois dans la mise en page :
function AriaLiveRegions() {
return (
<>
<div id="aria-live-polite" aria-live="polite" aria-atomic="true" className="sr-only" />
<div id="aria-live-assertive" aria-live="assertive" aria-atomic="true" className="sr-only" />
</>
);
}
┌─ Pyramide de test d'accessibilité ────────────────┐
│ │
│ ▲ Test manuel avec lecteur d'écran │
│ ╱ ╲ (VoiceOver, NVDA — trimestriel) │
│ ╱───╲ │
│ ╱ ╲ Playwright + axe-core E2E │
│ ╱ E2E ╲ (chaque page, porte CI) │
│ ╱─────────╲ │
│ ╱ ╲ Tests de composants jest-axe │
│ ╱ Composant ╲ (par composant interactif) │
│ ╱───────────────╲ │
│ ╱ ╲ ESLint jsx-a11y │
│╱ Linting statique╲ (à chaque commit) │
│╲___________________╱ │
└─────────────────────────────────────────────────────┘
Anti-patrons
divaveconClickau lieu debutton— les divs n'ont pas d'interaction clavier, ni de rôle, ni de focus. Utilisez des éléments sémantiques<button>ou<a>. Si vous devez utiliser une div, ajoutezrole="button",tabindex="0"etonKeyDownpour Entrée/Espace.aria-labelsur tout — le sur-étiquetage crée du bruit pour les utilisateurs de lecteurs d'écran. Préférez les étiquettes de texte visibles ; n'utilisezaria-labelque lorsque le texte visible est impossible.- Masquer les contours de focus —
outline: nonesans remplacement rend la navigation au clavier invisible. Utilisez:focus-visiblepour des indicateurs de focus réservés au clavier qui n'apparaissent pas lors d'un clic de souris. - Lien de navigation de saut manquant — les utilisateurs de clavier doivent tabuler à travers toute la navigation sur chaque page. Ajoutez
<a href="#main" class="sr-only focus:not-sr-only">Aller au contenu principal</a>comme premier élément focusable. - Notifications Toast sans régions live — les lecteurs d'écran ne peuvent pas détecter les toasts ajoutés dynamiquement. Utilisez les régions
aria-live="polite"pour les annoncer.
Liste de contrôle qualité
- La porte CI axe-core passe avec zéro violation sur toutes les pages
-
eslint-plugin-jsx-a11yactivé sans avertissements - Toutes les images ont un texte
altdescriptif (oualt=""pour les images décoratives) - Toutes les entrées de formulaire ont des éléments
<label>associés (pas seulement du texte de placeholder) - La hiérarchie des titres est séquentielle (
h1>h2>h3, pas de saut) - Points de repère de page présents :
<main>,<nav>,<header>,<footer> - Le lien de navigation de saut est le premier élément focusable
- Focus visible sur tous les éléments interactifs (styles
:focus-visibledéfinis) - Les modales piègent le focus et restaurent le focus à la fermeture
- Les changements de contenu dynamique sont annoncés via les régions
aria-live - Les ratios de contraste des couleurs respectent WCAG AA (4.5:1 texte normal, 3:1 texte grand)
- Toutes les fonctionnalités sont utilisables uniquement avec le clavier (Tab, Entrée, Espace, Échap, touches fléchées)