PHASE E — ACCESSIBILITÉ (WCAG 2.1 AA)
E1. Sémantique HTML
Éléments sémantiques
| Élément |
Occurrences |
Fichiers |
<main> |
2 |
Layout.tsx:37, DashboardLayout.tsx:45 |
<nav> |
2 |
Sidebar.tsx:146 (avec role="navigation" + aria-label), Navbar.tsx:88 |
<header> |
1 |
AuthLayout.tsx:48 |
<footer> |
0 |
❌ Absent |
<section> |
0 |
❌ Absent |
<article> |
0 |
❌ Absent |
<aside> |
0 |
❌ Absent |
Verdict : ⚠️ La sémantique HTML est minimale. Seuls <main> et <nav> sont utilisés. Aucun <section>, <article>, <aside>, <footer> détecté dans les composants. La Sidebar devrait être un <aside>, les cards d'articles un <article>, les sections de page des <section>.
Anti-patterns
- ✅ Aucun
<div onClick> ou <span onClick> sans role détecté — excellent
- ✅ Les éléments cliquables utilisent des boutons ou des éléments avec
role approprié
Heading hierarchy
- ⚠️ Impossible de valider la hiérarchie h1→h6 sans rendu — les headings sont présents dans les composants mais leur hiérarchie dépend de la composition des pages
E2. ARIA
Statistiques
| Attribut |
Occurrences |
Verdict |
aria-label |
100+ |
✅ Bien utilisé |
aria-hidden |
100+ |
✅ Icônes décoratives cachées |
aria-describedby |
15+ |
✅ Messages d'erreur liés |
aria-expanded |
12+ |
✅ Menus/accordéons |
aria-live |
25+ |
✅ Contenus dynamiques annoncés |
aria-required |
6 |
⚠️ Pourrait être plus systématique |
aria-pressed |
Présent |
✅ Boutons toggle |
aria-current |
Présent |
✅ Navigation |
aria-checked |
Présent |
✅ Checkboxes/switches |
aria-selected |
Présent |
✅ Tabs/options |
aria-disabled |
Présent |
✅ Éléments désactivés |
aria-valuemin/max/now |
Présent |
✅ Sliders/progress |
aria-invalid |
Présent |
✅ Champs en erreur |
aria-busy |
Présent |
✅ Chargements |
aria-haspopup |
Présent |
✅ Menus |
aria-labelledby |
Présent |
✅ Régions labellisées |
Roles
| Role |
Usage |
button |
Éléments interactifs |
navigation |
Sidebar, Navbar |
dialog |
Modals/dialogs |
alert |
Messages d'alerte |
status |
Messages de statut |
listbox / option |
Select |
menuitem |
Dropdown menu |
radiogroup / radio |
Radio groups |
slider |
Sliders |
progressbar |
Progress bars |
switch |
Toggles |
tab / tabpanel / tablist |
Tabs |
search |
Barre de recherche |
grid / row |
Tables |
toolbar |
Barres d'outils |
Verdict : ✅ L'usage ARIA est extensif et correct. Couverture remarquable.
E3. Focus management
Outline
| Problème |
Fichier:ligne |
Détail |
outline: none |
styles/global-effects.css:78,94,107 |
Suppression outline |
outline: none |
styles/fix-input-focus.css:18,44 |
Suppression outline avec !important |
outline: none |
styles/input.css:44 |
Suppression outline |
MAIS : La plupart des composants TSX utilisent focus-visible:ring-2 focus-visible:ring-ring — un indicateur de focus visible est systématiquement ajouté via Tailwind. Les suppressions d'outline dans les CSS sont compensées par des rings visibles dans les composants.
Verdict : ⚠️ Techniquement les outlines sont supprimées en CSS, mais remplacées par des focus rings visibles. Risque : si un élément interactif n'a pas la classe focus-visible:ring-*, il perd tout indicateur de focus.
Focus trap
- ✅
FocusTrap composant dédié (components/ui/focus-trap.tsx)
- ✅ Utilisé dans
Modal (modal.tsx:97)
- ✅ Utilisé dans
Header mobile menu (Header.tsx:138)
- ⚠️ Non vérifié dans tous les dialogs/drawers
Focus restauration
- [DONNÉES INSUFFISANTES — nécessite test runtime pour vérifier la restauration du focus après fermeture des modales]
Skip navigation
- ❌ Absent. Le test d'accessibilité (
__tests__/accessibility.test.tsx:408-422) cherche un skip link mais aucune implémentation n'existe dans le code.
- WCAG 2.4.1 Bypass Blocks — Violation
Tab order
- ✅
tabIndex utilisé correctement : 50+ instances
- ✅
tabIndex={0} pour éléments focalisables
- ✅
tabIndex={-1} pour éléments programmatiquement focalisables
- ✅ Conditional tabIndex selon l'état (disabled → -1)
E4. Contraste et couleurs
Estimation des ratios
| Combinaison |
Ratio estimé |
Verdict WCAG AA |
--foreground sur --background (light) |
~15:1 |
✅ Passe |
--foreground sur --background (dark) |
~14:1 |
✅ Passe |
--primary sur --background (light) |
~4.5:1 |
⚠️ Limite AA (OKLCH 0.75 sur 0.985) |
--primary sur --primary-foreground |
~8:1 |
✅ Passe |
--muted-foreground sur --background (light) |
~5:1 |
✅ Passe |
--muted-foreground sur --muted (dark) |
~3.5:1 |
⚠️ Limite pour texte normal |
--destructive sur --background |
~5:1 |
✅ Passe |
Note : Les couleurs OKLCH rendent l'estimation de contraste moins précise. Un test automatisé avec les valeurs rendues est recommandé.
Information par la couleur seule
- ⚠️ Les badges de succès/erreur/warning utilisent la couleur + une icône — ✅ bon
- ⚠️ Les erreurs de formulaire sont en rouge — nécessitent aussi un texte ou icône
- ✅ Les états actifs utilisent couleur + position/poids typographique
Mode sombre
- ✅ Complet — toutes les variables CSS ont des valeurs dark mode
- ✅ Contrast ratios ajustés pour le dark mode
- ⚠️ Muted text en dark mode pourrait être sous le seuil WCAG AA (4.5:1)
E5. Images et médias
Images
- ✅ Toutes les
<img> ont un attribut alt — aucune image sans alt détectée
- ✅ 40+ images décoratives avec
alt="" — bonne pratique
- ⚠️ Beaucoup d'images album/artwork ont
alt="" au lieu d'un alt descriptif — devrait être le titre de l'album/track
Médias audio
- [DONNÉES INSUFFISANTES — plateforme audio, nécessite vérification des contrôles de lecture et transcriptions]
E6. Formulaires accessibles
Labels
- ✅
htmlFor + id : 100+ instances correctes
- ✅ La plupart des inputs ont un label associé
- ⚠️ Quelques inputs n'utilisent que des placeholders sans label visible
Messages d'erreur
- ✅
aria-describedby : 15+ instances — erreurs liées aux champs
- ✅
aria-invalid : utilisé sur les champs en erreur
Required
- ⚠️
aria-required : seulement 6 instances — devrait être plus systématique sur tous les champs obligatoires
Autocomplete
- ✅
autoComplete : 10+ instances sur les champs auth (email, password, name)
- ✅ Bonne pratique sur les formulaires d'authentification
Tableau des violations
| Sévérité |
Critère WCAG |
Localisation |
Problème |
Correction |
| 🔴 CRITIQUE |
2.4.1 Bypass Blocks |
App-level |
Skip navigation absent |
Ajouter <a href="#main" class="sr-only focus:not-sr-only"> |
| 🟠 HAUTE |
1.3.1 Info & Relationships |
Toute l'app |
Aucun <section>, <article>, <aside>, <footer> |
Ajouter landmarks sémantiques |
| 🟠 HAUTE |
1.1.1 Non-text Content |
Artwork images |
alt="" sur images de contenu (albums, tracks) |
Ajouter alt descriptif (titre album/track) |
| 🟡 MOYENNE |
2.4.7 Focus Visible |
styles/global-effects.css, fix-input-focus.css |
Outline supprimé en CSS |
S'assurer que TOUS les éléments interactifs ont focus-visible:ring-* |
| 🟡 MOYENNE |
1.4.3 Contrast |
Dark mode muted text |
--muted-foreground potentiellement sous 4.5:1 |
Augmenter la luminosité à oklch(0.75+) |
| 🟡 MOYENNE |
3.3.2 Labels |
Formulaires divers |
aria-required sur seulement 6 champs |
Ajouter sur tous les champs obligatoires |
| 🟢 BASSE |
4.1.3 Status Messages |
Toasts |
aria-live présent mais pas sur tous les toasts |
Vérifier que tous les toasts ont role="alert" |
| 🟢 BASSE |
1.3.5 Input Purpose |
Certains formulaires |
autoComplete manquant sur certains inputs |
Ajouter autoComplete sur champs d'adresse, téléphone |
SCORE ACCESSIBILITÉ : 6.5/10
Points gagnés
| Point |
Score |
Justification |
| ARIA extensif |
+1.5 |
200+ attributs ARIA, roles corrects, aria-live |
| Focus management |
+1.0 |
FocusTrap, focus-visible rings, tabIndex correct |
| Form labels |
+1.0 |
htmlFor/id, aria-describedby, autoComplete |
| Images alt |
+0.8 |
100% des images ont alt |
| Icônes aria-hidden |
+0.7 |
100+ instances correctes |
| prefers-reduced-motion |
+0.5 |
3 instances de respect |
| ESLint jsx-a11y |
+0.5 |
Plugin actif |
Points perdus
| Point |
Score |
Justification |
| Skip navigation absent |
-1.0 |
Violation WCAG 2.4.1 critique |
| Sémantique HTML minimale |
-1.0 |
Pas de section, article, aside, footer |
| Images artwork sans alt descriptif |
-0.5 |
album/track covers avec alt="" |
| Outline CSS supprimé |
-0.3 |
Compensé par focus rings mais risque |
| aria-required insuffisant |
-0.2 |
Seulement 6 champs |
| Contraste dark mode muted |
-0.5 |
Potentiellement sous 4.5:1 |