veza/apps/web/dev_audit/frontend/05_accessibility_audit.md

172 lines
8.2 KiB
Markdown
Raw Permalink Normal View History

# Phase E — Accessibility Audit (WCAG 2.1 AA)
**Score Accessibilité : 5.5/10**
---
## E1. Sémantique HTML
### Éléments sémantiques détectés
| Élément | Occurrences | Fichiers |
|---------|------------|---------|
| `<main>` | 1 | `components/layout/Layout.tsx` |
| `<nav>` | 2 | `components/layout/Sidebar.tsx`, `Navbar.tsx` |
| `<header>` | 1 | `components/layout/Header.tsx` |
| `<footer>` | 0 | Aucun |
| `<section>` | 2 | Rare |
| `<article>` | 0 | Aucun |
| `<aside>` | 0 | Aucun |
**Verdict** : Sémantique HTML **insuffisante** pour une application de cette taille. La sidebar devrait être un `<aside>`, les cards de contenu devraient utiliser `<article>`, les sections de page `<section>`. La navigation par landmarks pour les utilisateurs de lecteurs d'écran est limitée. **-2 points.**
### Anti-patterns `div onClick`
- **2 occurrences** de `<div onClick>` identifiées :
- `components/social/groups/GroupCard.tsx:66``<div onClick={(e) => e.stopPropagation()}>` — pas d'élement interactif, arrêt propagation seulement
- `components/ui/dialog/DialogTrigger.tsx:15``<div onClick={onClick} style={{ display: 'inline-block' }}>`**devrait être un `<button>`**
### Heading hierarchy
- Hiérarchie définie dans `@layer base` [index.css:506-516] : h1→h6 avec tailles responsives ✅
- **Non vérifié** : L'ordre des headings dans les pages individuelles (h1 → h2 → h3) n'est pas garanti architecturalement. Risque de sauts (h1 → h3).
---
## E2. ARIA
### Usage global
- **176 fichiers** utilisent des attributs `aria-*` ✅ Bon niveau d'adoption
### Détail
| Attribut | Usage | Fichiers | Verdict |
|----------|-------|----------|---------|
| `aria-label` | Éléments interactifs sans texte visible | Large adoption | ✅ Bon |
| `aria-live` | Contenus dynamiques | 6+ fichiers (Toast, auth feedback, track list) | ⚠️ Partiel |
| `aria-expanded` | Menus/accordéons | 3+ fichiers (PlaybackSpeed, QualitySelector, Accordion) | ⚠️ Partiel |
| `aria-invalid` | Inputs en erreur | `input.tsx:31` | ✅ Systématique |
| `aria-describedby` | Messages d'erreur liés | `input.tsx:32` | ✅ |
| `aria-hidden` | Icônes décoratives | **Rarement utilisé** | ❌ Manquant |
| `role="button"` | Éléments cliquables non-boutons | 18 fichiers | ⚠️ Devrait être `<button>` |
### Problèmes identifiés
1. **`aria-hidden` absent** sur les icônes Lucide (226 fichiers). Les icônes purement décoratives devraient avoir `aria-hidden="true"`. **Violation WCAG 1.1.1.**
2. **`role="button"` sur des `<div>`** dans 18 fichiers [PlaylistCard, TrackCard, LibraryPageGrid, etc.] — ces éléments devraient être des `<button>` natifs pour bénéficier du focus clavier et de la gestion Enter/Space automatique.
3. **`aria-live` limité** — les zones dynamiques (résultats de recherche, listes filtrées, compteurs) n'annoncent pas systématiquement les changements.
---
## E3. Focus management
### `outline-none` sans remplacement
- **45 fichiers** utilisent `outline-none`
- **Majorité avec remplacement** : `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring` — pattern correct ✅
- **Exceptions problématiques** :
- `features/studio/components/cloud-file-browser/FileToolbar.tsx``outline-none` **sans ring**
- `CreateProductViewDetailsCard.tsx`, `EditProfileIdentityCard.tsx`, `AccountSettingsPreferencesCard.tsx``outline-none` sur inputs sans ring visible ❌
### Focus visible
- **56 fichiers** utilisent `focus-visible:`
- Le composant `Button` utilise `focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2` [button.tsx:12] ✅
- Le composant `Input` utilise `focus-visible:outline-none focus-visible:ring-2` [input.tsx:36] ✅
- **Glow effect** : `focus-visible:shadow-[var(--sumi-shadow-glow)]` [button.tsx:12, input.tsx:37] — feedback visuel amélioré ✅
### Focus trap
- `components/ui/focus-trap.tsx` (126L) disponible ✅
- Utilisé dans `modal.tsx` pour les modales ✅
- **Vérification manuelle nécessaire** : restauration du focus après fermeture de modale
### Skip navigation
-**Aucun skip navigation link** détecté. Violation WCAG 2.4.1 pour une application avec sidebar + header.
---
## E4. Contraste et couleurs
### Estimations de contraste (Dark mode)
| Combinaison | Ratio estimé | WCAG AA (4.5:1) |
|-------------|-------------|-----------------|
| `--sumi-text-primary` (#f0ede8) sur `--sumi-bg-base` (#121215) | ~14:1 | ✅ |
| `--sumi-text-secondary` (#a8a4a0) sur `--sumi-bg-base` (#121215) | ~8:1 | ✅ |
| `--sumi-text-tertiary` (#706c68) sur `--sumi-bg-base` (#121215) | ~4.2:1 | ⚠️ Borderline |
| `--sumi-text-disabled` (#4a4844) sur `--sumi-bg-base` (#121215) | ~2.5:1 | ❌ Insuffisant |
| `--sumi-accent` (#7c9dd6) sur `--sumi-bg-base` (#121215) | ~7:1 | ✅ |
| `--sumi-vermillion` (#d4634a) sur `--sumi-bg-base` (#121215) | ~5:1 | ✅ |
### Information par couleur seule
- **Erreurs** : Combinaison couleur rouge + icône AlertTriangle + texte — ✅ pas couleur seule
- **Succès** : Combinaison couleur verte + icône + texte — ✅
- **Toast** : Icône + texte + couleur — ✅
---
## E5. Images et médias
### Images sans `alt`
- **2 images** sans attribut `alt` :
1. `components/social/groups/CreateGroupModal.tsx:62``<img src={coverImage}>`
2. `components/upload/metadata/CoverArtUploadModal.tsx:124``<img src={currentImage}>`
### `loading="lazy"`
- **0 occurrence** de `loading="lazy"` sur les `<img>`
- Le composant `OptimizedImage` gère le lazy loading via IntersectionObserver [optimized-image/OptimizedImage.tsx] mais ce n'est pas la même chose que l'attribut natif `loading="lazy"`.
---
## E6. Formulaires accessibles
### Labels
| Critère | Status | Détail |
|---------|--------|--------|
| `htmlFor` + `id` | ⚠️ Partiel | 56 fichiers — pas systématique |
| `aria-describedby` pour erreurs | ✅ | Input component [input.tsx:32] |
| `aria-required` | ❌ | Pas utilisé systématiquement |
| `aria-invalid` | ✅ | Input component [input.tsx:31] |
| `autocomplete` | ❌ | Pas vérifié systématiquement |
---
## Tableau des violations
| Sévérité | Critère WCAG | Fichier(s) | Problème | Correction |
|----------|-------------|------------|----------|------------|
| 🔴 CRITIQUE | 2.4.1 Bypass Blocks | Global | Pas de skip navigation link | Ajouter `<a href="#main-content" class="sr-only focus:not-sr-only">Skip to content</a>` |
| 🟠 HAUTE | 4.1.2 Name Role Value | 18 fichiers | `role="button"` sur div sans keyboard handler | Remplacer par `<button>` natif |
| 🟠 HAUTE | 1.1.1 Non-text Content | 226 fichiers | `aria-hidden` manquant sur icônes décoratives | Ajouter `aria-hidden="true"` sur icônes sans label |
| 🟠 HAUTE | 1.3.1 Info Relationships | Global | Sémantique HTML insuffisante (1 `<main>`, 0 `<aside>`, 0 `<article>`) | Enrichir les landmarks |
| 🟡 MOYENNE | 1.1.1 Non-text Content | `CreateGroupModal.tsx:62`, `CoverArtUploadModal.tsx:124` | `<img>` sans `alt` | Ajouter `alt` descriptif |
| 🟡 MOYENNE | 2.4.7 Focus Visible | `FileToolbar.tsx` + 3 fichiers | `outline-none` sans ring de remplacement | Ajouter `focus-visible:ring-2` |
| 🟡 MOYENNE | 4.1.3 Status Messages | Zones dynamiques | `aria-live` insuffisant pour les résultats de recherche/filtrage | Ajouter `aria-live="polite"` sur les zones de résultats |
| 🟢 BASSE | 1.4.3 Contrast | `--sumi-text-disabled` | Ratio ~2.5:1 (sous le seuil AA de 4.5:1) | Acceptable pour texte désactivé (non interactif) |
---
## Score Accessibilité détaillé
| Critère | Points | Justification |
|---------|--------|---------------|
| ARIA usage | +1.5 | 176 fichiers, bonne adoption |
| Input accessibility | +1 | aria-invalid, aria-describedby, error states |
| Focus visible | +1 | 56 fichiers, glow effect |
| Focus trap modales | +0.5 | Composant dédié |
| Sémantique HTML | -1.5 | 1 main, 0 aside, 0 article — insuffisant |
| Skip navigation | -0.5 | Absent |
| aria-hidden icônes | -0.5 | 226 fichiers sans aria-hidden |
| role="button" sur div | -0.5 | 18 fichiers |
| img alt | -0.25 | 2 images |
| outline-none sans ring | -0.25 | 4 fichiers |
| Contraste | +0.5 | Majoritairement bon |
| **Total** | **5.5/10** | **Efforts visibles mais lacunes structurelles** |