ARIA
Enrichir la sémantique des composants complexes pour les rendre accessibles
Qu'est-ce que ARIA ?
ARIA (Accessible Rich Internet Applications) est une spécification du W3C qui permet d'améliorer l'accessibilité des applications web riches et des composants dynamiques.
🏷️ Rôles
Définissent ce qu'est un élément (button, navigation, alert...)
role="button"📊 États
Décrivent l'état actuel d'un élément (checked, expanded, disabled...)
aria-expanded="true"🔧 Propriétés
Fournissent des informations supplémentaires (label, describedby, controls...)
aria-label="Menu principal"⚠️ Règle d'or d'ARIA
Ne pas utiliser ARIA si vous pouvez utiliser HTML natif
Formulaires avec attributs ARIA
Mauvais exemple
Formulaire sans ARIA
❌ Formulaire inaccessible sans attributs ARIA appropriés :
<!-- Mauvais : pas de label, pas de description -->
<input type="text" placeholder="Votre email">
<!-- Mauvais : icône non accessible -->
<button>
<span class="icon">🔍</span>
</button>
<!-- Mauvais : erreur non associée, pas d'autocomplete -->
<input type="password">
<div class="error">Mot de passe trop court</div>Bon exemple
Formulaire avec ARIA
✅ Formulaire accessible avec attributs ARIA appropriés :
<!-- Bon : aria-label pour champ sans label visible -->
<input
type="text"
aria-label="Adresse email (obligatoire)"
aria-describedby="email-desc"
aria-required="true">
<span id="email-desc">Format: nom@exemple.com</span>
<!-- Bon : bouton avec aria-label -->
<button aria-label="Envoyer le formulaire">
<span aria-hidden="true">🔍</span>
</button>
<!-- Bon : erreur associée avec aria-describedby -->
<input
type="password"
aria-label="Mot de passe (8 caractères minimum)"
aria-invalid="true"
aria-describedby="pwd-error"
autocomplete="new-password">
<div id="pwd-error" role="alert">
Le mot de passe doit contenir au moins 8 caractères
</div>Attributs ARIA utilisés :
aria-label: Fournit un label accessiblearia-describedby: Associe une description détailléearia-required: Indique un champ obligatoirearia-invalid: Signale un champ invalidearia-labelledby: Référence un label existantrole="alert": Annonce immédiatement les erreursaria-hidden: Cache les éléments décoratifsaria-live: Annonce les changements dynamiques
Explication : Les attributs ARIA enrichissent les formulaires en fournissant des labels, descriptions et états aux technologies d'assistance.
Accordéon accessible
Mauvais exemple
Sans attributs ARIA
<!-- Bad: No ARIA attributes, no keyboard navigation -->
<div class="accordion-header" @click="toggle">
<span>Title</span>
<span>+</span>
</div>
<div v-if="open">
Content
</div>Bon exemple
Avec ARIA correct
<!-- Good: Complete ARIA with keyboard navigation -->
<button
id="accordion-button-1"
aria-expanded="false"
aria-controls="panel-1"
@click="toggle"
@keydown.enter="toggle"
>
Title
</button>
<div
id="panel-1"
role="region"
aria-labelledby="accordion-button-1"
:hidden="!open"
>
Content
</div>Explication : Un accordéon doit utiliser les attributs ARIA appropriés, supporter la navigation clavier et annoncer les changements d'état.
Tabindex et navigation clavier
L'attribut tabindex contrôle si un élément peut recevoir le focus clavier et dans quel ordre.
✅ tabindex="0"
L'élément est focusable dans l'ordre naturel du DOM
tabindex="0"<!-- Good: Interactive element focusable -->
<div role="button" aria-label="Custom action" tabindex="0">
Custom button
</div>⚠️ tabindex="-1"
Focusable par JavaScript mais pas par Tab
tabindex="-1"<!-- Good: For programmatic focus -->
<div role="alert" aria-live="assertive" tabindex="-1">
Error message
</div>❌ tabindex > 0
Change l'ordre de navigation (à éviter !)
tabindex="5"<!-- Bad: Breaks natural tab order -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>📋 Bonnes pratiques tabindex
- Utilisez
tabindex="0"pour rendre les éléments custom interactifs focusables - Utilisez
tabindex="-1"pour le focus programmatique (modals, alerts) - N'utilisez JAMAIS de valeurs positives - respectez l'ordre du DOM
- Les éléments natifs interactifs (
<button>,<a>,<input>) n'ont pas besoin de tabindex
🎯 Exemples interactifs
Testez la navigation au clavier (Tab/Shift+Tab) avec ces éléments :
❌ Éléments non-focusables
Paragraphe
💡 Conseils pour tester
- Utilisez Tab pour naviguer vers l'avant
- Utilisez Shift + Tab pour naviguer vers l'arrière
- Utilisez Espace ou Entrée pour activer les éléments
- Observez l'indicateur de focus (contour bleu)
- Notez quels éléments peuvent ou ne peuvent pas recevoir le focus
Menu déroulant accessible
Mauvais exemple
Menu sans ARIA
<!-- Bad: No menu role, no focus trap -->
<div @click="open = !open">
Actions ▼
</div>
<div v-if="open">
<div @click="action">Edit</div>
<div @click="action">Delete</div>
</div>Bon exemple
Menu avec ARIA
<!-- Good: ARIA attributes and focus management -->
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="menu"
@keydown.escape="close"
>
Actions
</button>
<div
id="menu"
role="menu"
v-if="open"
>
<button role="menuitem">Edit</button>
<button role="menuitem">Delete</button>
</div>Explication : Un menu déroulant doit supporter la navigation clavier, fermer avec Échap, et maintenir le focus de manière appropriée.
Galerie d'images accessible
Mauvais exemple
Galerie d'images accessible
<!-- ❌ Mauvais : galerie non accessible au clavier -->
<div class="thumbnails">
<img v-for="image in images"
:src="image.thumb"
@click="selectImage(index)"
:class="{ active: selected === index }" />
</div>
<div class="main-image">
<img :src="images[selected].full" />
</div>Bon exemple
Galerie d'images accessible
Vue frontale du MacBook Pro 16 pouces avec l'écran fermé, montrant le design épuré en aluminium.
<!-- ✅ Bon : galerie accessible avec navigation clavier -->
<div role="tablist" aria-label="Images du produit">
<button v-for="(image, index) in images"
:id="`tab-${index}`"
role="tab"
:aria-selected="selected === index"
:aria-controls="`panel-${index}`"
:tabindex="selected === index ? 0 : -1"
@click="selectImage(index)"
@keydown="handleKeydown($event, index)">
<img :src="image.thumb" :alt="`${image.alt} - Miniature`" />
</button>
</div>
<div :id="`panel-${selected}`"
role="tabpanel"
:aria-labelledby="`tab-${selected}`">
<img :src="images[selected].full" :alt="images[selected].alt" />
<p>{{ images[selected].description }}</p>
</div>
<!-- Bonnes pratiques :
- Pattern tablist/tab/tabpanel ARIA
- Navigation avec flèches, Home, End
- Focus management avec tabindex
- Descriptions détaillées pour chaque image -->Explication : Une galerie d'images doit permettre la navigation au clavier et fournir des descriptions contextuelles pour chaque image. Le pattern ARIA tablist/tab/tabpanel est parfait pour ce cas d'usage.
Tableau triable accessible
Mauvais exemple
| Nom ↑ | Rôle | Statut | |
|---|---|---|---|
| Alice Martin | alice@example.com | Admin | Actif |
| Bob Dupont | bob@example.com | Utilisateur | Inactif |
| Charlie Leroy | charlie@example.com | Modérateur | Actif |
| Diana Moreau | diana@example.com | Utilisateur | Suspendu |
Bon exemple
| Alice Martin | alice@example.com | Admin | Actif |
|---|---|---|---|
| Bob Dupont | bob@example.com | Utilisateur | Inactif |
| Charlie Leroy | charlie@example.com | Modérateur | Actif |
| Diana Moreau | diana@example.com | Utilisateur | Suspendu |
Explication : Un tableau triable doit indiquer l'état de tri courant, permettre la navigation clavier, et annoncer les changements aux lecteurs d'écran.
Zones dynamiques aria-live
Mauvais exemple
Sans aria-live
Les changements ne sont pas annoncés
<!-- Mauvais : pas d'attribut aria-live -->
<div class="status-banner">
<p></p>
</div>Compteur : 0
Problèmes :
- Les changements ne sont pas annoncés
- Les utilisateurs de lecteurs d'écran ne savent pas que le contenu a changé
- Aucune indication de l'importance du message
Bon exemple
Avec aria-live approprié
Les changements sont annoncés selon leur priorité
<!-- Bon : avec aria-live="polite" pour informations -->
<div
class="status-banner"
aria-live="polite"
aria-atomic="true"
role="status"
>
<p>Information : Données mises à jour</p>
</div>
<!-- Bon : avec aria-live="assertive" pour alertes -->
<div
class="alert-banner"
aria-live="assertive"
aria-atomic="true"
role="alert"
>
<p>Attention : Action requise immédiatement</p>
</div>Zone polite (informations)
Zone assertive (alertes)
Avantages :
- Les messages importants interrompent avec aria-live="assertive"
- Les messages informatifs attendent avec aria-live="polite"
- aria-atomic="true" annonce tout le contenu de la région
- Les rôles appropriés (status, alert) donnent plus de contexte
Explication : Les zones aria-live permettent d'annoncer des changements de contenu aux lecteurs d'écran. 'polite' attend la fin de l'annonce en cours, 'assertive' interrompt immédiatement.
Boutons avec icônes accessibles
Mauvais exemple
Bon exemple
Explication : Les boutons qui contiennent uniquement des icônes doivent avoir un aria-label ou du texte masqué visuellement pour être compris par les lecteurs d'écran.
Combobox / Recherche avec suggestions
Mauvais exemple
Sans ARIA ni navigation clavier
Bon exemple
Avec ARIA et navigation clavier
Explication : Une recherche avec suggestions (combobox) doit être navigable au clavier, annoncer les résultats et permettre la sélection.
Notifications Toast (Live Regions)
Mauvais exemple
Sans aria-live
❌ Les notifications ne sont pas annoncées aux lecteurs d'écran
<!-- Mauvais : pas annoncé aux lecteurs d'écran -->
<div v-if="showToast" class="toast">
Votre demande a été enregistrée
</div>Bon exemple
Avec aria-live approprié
✅ Les notifications sont correctement annoncées
<!-- Bon : notification info (polite) -->
<div
v-if="showToast"
role="status"
aria-live="polite"
aria-atomic="true"
class="toast"
>
<p>Votre demande a été enregistrée</p>
<button @click="dismiss" aria-label="Fermer notification">✕</button>
</div>
<!-- Bon : alerte erreur (assertive) -->
<div
v-if="showError"
role="alert"
aria-live="assertive"
aria-atomic="true"
class="toast error"
>
<p>Erreur : action impossible à exécuter</p>
<button @click="dismiss" aria-label="Fermer alerte">✕</button>
</div>Différences :
role="status"+aria-live="polite": Pour les notifications informatives, n'interrompt pas la lecture en coursrole="alert"+aria-live="assertive": Pour les erreurs critiques, interrompt immédiatementaria-atomic="true": Annonce tout le contenu de la notification
Explication : Les notifications dynamiques doivent être annoncées aux lecteurs d'écran sans voler le focus. Utilisez role='status' pour les infos et role='alert' pour les erreurs.