veza/apps/web/dev_audit/frontend/05_accessibility_audit.md
senke 5f88c56113 fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header

Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 10:13:44 +01:00

8.2 KiB

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.tsxoutline-none sans ring
    • CreateProductViewDetailsCard.tsx, EditProfileIdentityCard.tsx, AccountSettingsPreferencesCard.tsxoutline-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