fix(a11y): rend conformes les boîtes de dialogue et les messages dynamiques

Le dossier affirmait un focus trap sur les modales et des régions aria-live
pour les mises à jour dynamiques. Ni l'un ni l'autre n'existait : Modal.jsx
tenait en 19 lignes, sans role="dialog", sans aria-modal, sans gestion du
clavier, et aucune occurrence d'aria-live dans le front. Plutôt que retirer
ces affirmations, elles sont rendues vraies.

Modal :
- role="dialog", aria-modal et aria-labelledby relié au titre
- focus placé à l'ouverture sur le premier champ du corps, à défaut sur le
  premier élément focusable
- tabulation contenue dans la modale, dans les deux sens
- fermeture par Échap et restitution du focus à l'élément d'origine
- identifiant produit par useId : ni tirage aléatoire pendant le rendu, ni
  collision entre deux modales

ErrorMessage et LoadingSpinner reçoivent role="alert"/"status" avec le niveau
d'annonce correspondant ; les éléments purement décoratifs passent en
aria-hidden.

9 tests ajoutés pour que ces comportements soient vérifiables autrement qu'en
relisant le code (41 -> 50 tests front).
This commit is contained in:
2026-08-16 13:34:09 +02:00
parent fc3cde3d65
commit 766d853d30
4 changed files with 161 additions and 6 deletions
@@ -4,9 +4,12 @@ import { AlertCircle } from 'lucide-react';
import styles from './ErrorMessage.module.css';
export default function ErrorMessage({ message = 'Une erreur est survenue.', onRetry }) {
/* role="alert" annonce l'erreur immédiatement au lecteur d'écran : elle
apparaît après le chargement de la page, sans quoi elle passerait
inaperçue pour un utilisateur non voyant. */
return (
<div className={styles.container}>
<AlertCircle size={20} color="var(--color-primary)" />
<div className={styles.container} role="alert" aria-live="assertive">
<AlertCircle size={20} color="var(--color-primary)" aria-hidden="true" />
<p className={styles.text}>{message}</p>
{onRetry && (
<button className={styles.retryBtn} onClick={onRetry}>