WAI-ARIA définit plus de 80 rôles. Classés dans l’ordre de la spécification, ils forment une liste qui décourage autant qu’elle renseigne. Cette taxonomie les regroupe par usage réel, ce que tu rencontres concrètement en audit et en intégration.
Quatre familles couvrent 95% des situations : les landmarks, les widgets interactifs, la structure de contenu, et les live regions.
Famille 1 : Les landmarks (navigation structurelle)
Les rôles landmark définissent les grandes zones d’une page. Ils alimentent la liste des régions dans les lecteurs d’écran (touche D dans NVDA, W dans VoiceOver), permettant de sauter directement à la zone souhaitée sans parcourir tout le contenu.
Chaque rôle landmark a un équivalent HTML5 natif. Toujours préférer l’élément HTML natif, il produit automatiquement le landmark sans attribut supplémentaire.
| Rôle ARIA | Équivalent HTML | Annonce NVDA | Usage |
|---|---|---|---|
banner | <header> (hors <main>) | “en-tête” | En-tête de site, une seule fois par page |
main | <main> | ”principal” | Contenu principal, une seule fois |
navigation | <nav> | ”navigation” | Menus de navigation |
complementary | <aside> | ”complémentaire” | Contenu secondaire lié |
contentinfo | <footer> (hors <main>) | “pied de page” | Pied de site, une seule fois |
search | <search> (HTML 5.3) | “recherche” | Formulaire de recherche |
form | <form> avec aria-label | ”formulaire” | Formulaire avec un nom explicite |
region | <section> avec aria-labelledby | ”région” | Zone nommée, usage à limiter |
La règle du label obligatoire
Quand plusieurs landmarks du même rôle coexistent sur une page, chacun doit avoir un label distinct. Sans label, le lecteur d’écran annonce “navigation, navigation, navigation” dans sa liste. Impossible de les distinguer.
<!-- ✗ Deux <nav> sans distinction -->
<nav>
<ul><!-- menu principal --></ul>
</nav>
<nav>
<ul><!-- menu pied de page --></ul>
</nav>
<!-- ✓ Labels distincts avec aria-label -->
<nav aria-label="Navigation principale">
<ul><!-- menu principal --></ul>
</nav>
<nav aria-label="Navigation pied de page">
<ul><!-- menu pied de page --></ul>
</nav>
La même règle s’applique à <main> (unique, donc pas besoin de label), <aside> (label si plusieurs), <section> (label toujours requis pour générer le landmark).
role="region" est à utiliser avec parcimonie
<section> avec aria-labelledby génère un landmark region. C’est utile pour les zones de contenu importantes et nommées. Mais multiplier les regions surcharge la liste des landmarks et nuit à la navigation.
Bonne pratique : utiliser region uniquement pour les zones que l’utilisateur voudra atteindre directement, comme un formulaire d’inscription, un tableau de bord, une zone de résultats de recherche, etc.
Ce que le lecteur d’écran annonce
<!-- Annonces NVDA en mode navigation (F6 entre landmarks) -->
<header> → "en-tête, région de repère"
<nav aria-label="Principale"> → "Principale, navigation, région de repère"
<main> → "principal, région de repère"
<aside aria-label="À lire aussi"> → "À lire aussi, complémentaire, région de repère"
<footer> → "pied de page, région de repère"
Critères RGAA : 9.2 (structure cohérente) , 12.6 (zones atteignables ou évitables) , 12.7 (lien d’évitement vers le contenu principal) .
Famille 2 : Les widgets interactifs
Les rôles widget communiquent la nature des composants interactifs. Chaque rôle engage à implémenter le comportement clavier correspondant documenté dans l’ ARIA APG lien externe qui s'ouvre dans un nouvel onglet .
Boutons et contrôles simples
role="button"
À n’utiliser que si <button> est impossible (contrainte technique rare). Engage à implémenter tabindex="0", activation sur Entrée et Espace.
<!-- Cas légitime : un <div> existant qu'on ne peut pas changer -->
<div role="button" tabindex="0"
onclick="basculer()"
onkeydown="if(e.key==='Enter'||e.key===' ')basculer()">
Ouvrir le menu
</div>
État associé courant : aria-expanded (pour les déclencheurs de disclosure), aria-pressed (pour les toggle buttons).
role="switch"
Variante de bouton pour les interrupteurs on/off (dark mode, notifications…). Annonce l’état comme “activé” ou “désactivé” plutôt que “coché”.
<button role="switch" aria-checked="true">
Mode sombre
</button>
→ NVDA : "Mode sombre, commutateur, activé"
role="checkbox" et role="radio"
Réservés aux cas où <input type="checkbox"> et <input type="radio"> sont impossibles. Nécessitent aria-checked et la gestion clavier complète.
Widgets de navigation interne
role="tab" / role="tablist" / role="tabpanel"
La triade des onglets. Structure obligatoire :
<div role="tablist" aria-label="Sections du formulaire">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">Informations</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">Coordonnées</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- contenu panneau 1 -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- contenu panneau 2 -->
</div>
Points critiques :
- Un seul onglet dans le
tabindexnaturel (celui sélectionné) - Les autres onglets ont
tabindex="-1", navigation entre eux aux flèches gauche/droite aria-selectedsur chaquetab, pasaria-expandedaria-controlspointe vers letabpanelcorrespondant
role="menu" / role="menuitem"
Réservé aux menus d’application (comme les menus Fichier/Édition/Affichage d’un logiciel). À ne pas utiliser pour la navigation principale d’un site, ce n’est pas un menu d’application, c’est une liste de liens.
<!-- ✗ Erreur fréquente : menu de navigation avec role="menu" -->
<nav>
<ul role="menu">
<li role="menuitem"><a href="/accueil">Accueil</a></li>
</ul>
</nav>
<!-- ✓ Navigation principale avec indication de la page active -->
<nav aria-label="Navigation principale">
<ul>
<li><a href="/accueil" aria-current="page">Accueil</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
aria-current="page" identifie le lien correspondant à la page en cours. Sans lui, les utilisateurs de lecteur d’écran doivent deviner quelle page est active, ou le déduire du titre de la page (j’aborderai ce sujet des propriétés et des états dans un prochain article).Le rôle menu impose un comportement clavier spécifique (flèches, Échap) que les utilisateurs n’attendent pas sur une navigation de site. C’est un anti-pattern courant dans les menus WordPress.
Widgets de sélection
role="listbox" / role="option"
Alternative custom à <select>. Structure :
<div role="listbox" aria-label="Choisir un département" aria-activedescendant="opt-29">
<div role="option" id="opt-29" aria-selected="true">Finistère (29)</div>
<div role="option" id="opt-56" aria-selected="false">Morbihan (56)</div>
</div>
Comportement clavier : flèches haut/bas pour naviguer, Espace ou Entrée pour sélectionner, recherche par frappe de lettre.
role="combobox"
Combinaison d’un champ texte et d’une liste déroulante (autocomplete). Le rôle le plus complexe à implémenter correctement : préférer une librairie testée (Downshift, Headless UI) plutôt qu’une implémentation maison.
Widgets de dialogue
role="dialog"
Pour les modales et boîtes de dialogue. Trois règles impératives :
- Focus piégé : la tabulation reste dans la modale tant qu’elle est ouverte
- Focus initial : au premier élément interactif de la modale à l’ouverture
- Focus restauré : au déclencheur d’origine à la fermeture
<div role="dialog"
aria-labelledby="titre-modal"
aria-describedby="description-modal"
aria-modal="true">
<h2 id="titre-modal">Confirmer la suppression</h2>
<p id="description-modal">Cette action est irréversible.</p>
<button>Confirmer</button>
<button>Annuler</button>
</div>
aria-modal="true" indique aux technologies d’assistance (AT) que le contenu derrière la modale est inerte. Attention : son support est inégal. Il faut aussi appliquer inert sur le contenu de fond ou gérer aria-hidden sur <body>.
role="alertdialog"
Variante urgente de dialog, annoncé immédiatement à l’ouverture, comme un alert. Réservé aux confirmations critiques (suppression, erreur bloquante).
Widgets d’information
role="tooltip"
Contenu informatif court apparaissant au survol ou à la prise de focus. Le déclencheur doit référencer le tooltip via aria-describedby.
<button aria-describedby="aide-champ">
Supprimer
</button>
<div role="tooltip" id="aide-champ">
Supprime définitivement cet élément. Action irréversible.
</div>
Le tooltip ne doit pas contenir d’éléments interactifs, il est lu comme description, pas comme zone navigable.
role="slider"
Curseur de valeur numérique. Requiert aria-valuemin, aria-valuemax, aria-valuenow et optionnellement aria-valuetext pour une valeur lisible (“50%” plutôt que “50”).
<div role="slider"
tabindex="0"
aria-label="Volume"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="70"
aria-valuetext="70%">
</div>
→ NVDA : "Volume, curseur, 70%"
Critères RGAA : 7.1 (compatibilité AT) , 7.3 (contrôle clavier) , 11.1 et 11.2 (labels de champs).
Famille 3 : La structure de contenu
Ces rôles décrivent l’organisation du contenu textuel et tabulaire. La plupart ont un équivalent HTML natif, toujours préférer le HTML natif.
Titres et listes
role="heading" avec aria-level
Uniquement pour les titres dont le niveau doit être différent de leur niveau sémantique HTML : cas très rares en pratique (composants dynamiques, widgets).
<!-- Cas légitime : titre généré dynamiquement dont le niveau varie -->
<div role="heading" aria-level="3">
Section dynamique
</div>
<!-- Dans 99% des cas, utiliser simplement : -->
<h3>Section dynamique</h3>
role="list" et role="listitem"
À utiliser uniquement si les styles CSS ont supprimé la sémantique de liste native. Certain navigateurs retirent le rôle list d’un <ul> stylé avec list-style: none.
<!-- Récupérer la sémantique de liste supprimée par CSS -->
<ul role="list" style="list-style: none; padding: 0;">
<li role="listitem">Élément 1</li>
</ul>
Tableaux
role="table" / role="row" / role="cell" / role="columnheader" / role="rowheader"
Pour les tableaux de données construits sans <table>, layouts CSS qui ressemblent à des tableaux mais n’utilisent pas les balises natives.
<!-- ✗ Grille CSS sans sémantique de tableau -->
<div class="grille-horaires">
<div class="en-tete">Lundi</div>
<div class="en-tete">Mardi</div>
<div>09h-12h</div>
<div>Fermé</div>
</div>
<!-- ✓ ARIA pour récupérer la sémantique manquante -->
<div role="table" aria-label="Horaires d'ouverture">
<div role="row">
<div role="columnheader">Lundi</div>
<div role="columnheader">Mardi</div>
</div>
<div role="row">
<div role="cell">09h-12h</div>
<div role="cell">Fermé</div>
</div>
</div>
<!-- ✓✓ Mais <table> reste toujours préférable -->
<table>
<caption>Horaires d'ouverture</caption>
<thead>
<tr><th>Lundi</th><th>Mardi</th></tr>
</thead>
<tbody>
<tr><td>09h-12h</td><td>Fermé</td></tr>
</tbody>
</table>
Critères RGAA : 5.1 (en-têtes de tableau) , 5.2 (en-têtes pertinents) , 9.1 (hiérarchie des titres) , 9.3 (listes), 9.4 (citations).
Images et figures
role="img"
Deux usages légitimes :
<!-- 1. SVG inline sans <title> — role="img" + aria-label -->
<svg role="img" aria-label="Logo de la commune de Nom_ville" viewBox="0 0 100 100">
<path d="..."/>
</svg>
<!-- 2. Groupe d'éléments formant une image composite -->
<div role="img" aria-label="Résultat : 3 étoiles sur 5">
★★★☆☆
</div>
role="figure"
Équivalent de <figure>. À utiliser si <figure> n’est pas accessible (CMS imposant un div).
role="presentation" et role="none"
Synonymes (préférer none en ARIA 1.1+). Supprime toute sémantique de l’élément et de ses descendants pour les AT. Usage : tableaux de mise en forme, éléments décoratifs avec structure.
<!-- Tableau de mise en forme hérité — supprimer la sémantique trompeuse -->
<table role="presentation">
<tr>
<td><!-- colonne gauche --></td>
<td><!-- colonne droite --></td>
</tr>
</table>
Attention : role="none" sur un élément focusable est ignoré par la plupart des AT, l’élément reste annoncé. Ne pas confondre avec aria-hidden="true".
Critères RGAA : 1.1 (alternative textuelle), 1.2 (images décoratives).
Famille 4 : Les live regions
Les live regions permettent aux AT d’annoncer automatiquement les changements de contenu sans que l’utilisateur ait déplacé son focus. Elles sont essentielles pour les interfaces dynamiques.
role="alert"
Annonce immédiate et prioritaire (assertive). Réservé aux messages urgents : erreurs, alertes de sécurité, actions destructives.
<!-- L'insertion de texte dans ce div est annoncée immédiatement -->
<div role="alert" id="zone-erreur"></div>
<script>
document.getElementById('zone-erreur').textContent =
'Erreur : votre session a expiré.';
</script>
→ NVDA interrompt la lecture en cours et annonce le message
Ne pas mettre role="alert" sur un élément déjà présent avec du contenu au chargement, il ne sera pas annoncé. La live region doit être vide au chargement, le contenu est injecté ensuite.
role="status"
Annonce différée et non prioritaire (polite). Pour les confirmations non urgentes : enregistrement réussi, résultats chargés, progression.
<div role="status" id="confirmation"></div>
<script>
// Après sauvegarde réussie
document.getElementById('confirmation').textContent =
'Modifications enregistrées.';
</script>
→ NVDA attend la fin de la lecture en cours avant d'annoncer
role="log"
Pour les journaux d’activité, liste de messages ajoutés chronologiquement (chat, historique de commandes, flux d’activité). Annonce polite par défaut.
role="timer"
Pour les compteurs de temps. Annonce polite. À utiliser avec aria-live="off" si le compteur se met à jour très fréquemment pour ne pas saturer les AT d’annonces.
<div role="timer" aria-live="off" id="compte-a-rebours">
02:30
</div>
role="marquee"
Contenu défilant en continu (bandeau d’actualités). Comportement assertive à éviter si possible car très intrusif pour les utilisateurs AT.
La règle d’injection
Toutes les live regions suivent la même règle : la région doit exister dans le DOM avant l’injection du message. Si tu crées la région et injectes le message en même temps, la plupart des AT manquent l’annonce.
// ✗ Création et injection simultanées — souvent manqué par les AT
const alerte = document.createElement('div');
alerte.setAttribute('role', 'alert');
alerte.textContent = 'Erreur de connexion.';
document.body.appendChild(alerte);
// ✓ Région pré-existante dans le HTML, injection seule
document.getElementById('zone-alert').textContent = 'Erreur de connexion.';
// ✓ Ou vider puis remplir pour forcer la relecture
const zone = document.getElementById('zone-status');
zone.textContent = '';
requestAnimationFrame(() => {
zone.textContent = 'Modifications enregistrées.';
});
Critère RGAA : 7.5 (messages de statut restitués par les AT) .
Récapitulatif — Quel rôle pour quel besoin ?
| Besoin | Première option | Si HTML impossible |
|---|---|---|
| En-tête de site | <header> | role="banner" |
| Navigation | <nav> | role="navigation" |
| Contenu principal | <main> | role="main" |
| Bouton | <button> | role="button" + tabindex + clavier |
| Case à cocher | <input type="checkbox"> | role="checkbox" + aria-checked + clavier |
| Onglets | — | role="tablist" + role="tab" + role="tabpanel" |
| Modale | <dialog> | role="dialog" + focus trap |
| Message d’erreur urgent | — | role="alert" |
| Confirmation non urgente | — | role="status" |
| Titre | <h1>-<h6> | role="heading" + aria-level |
| Liste | <ul>/<ol> | role="list" + role="listitem" |
| Tableau de données | <table> | role="table" + rôles enfants |
| Image SVG | <img alt="..."> | <svg role="img" aria-label="..."> |
| Élément décoratif | alt="" | role="none" ou aria-hidden="true" |
Les erreurs de rôle les plus fréquentes en audit
1. role="menu" sur la navigation principale
Impose un comportement clavier (flèches) que les utilisateurs n’attendent pas. Utiliser <nav> + <ul> + <li> + <a>.
2. role="button" sur un <a> avec href
Un lien avec role="button" est annoncé “bouton” mais se comporte comme un lien (activation sur Entrée uniquement, pas Espace). Choisir : lien (<a href>) ou bouton (<button>), pas les deux.
3. role="presentation" sur un élément focusable
Ignoré par les AT sur les éléments interactifs. L’élément reste annoncé avec son rôle natif.
4. Live region créée et remplie simultanément Le message est manqué. Toujours pré-créer la région dans le HTML.
5. role="img" sans aria-label
Crée une image sans alternative qui est annoncée “image” sans description. Toujours accompagner role="img" d’un aria-label ou aria-labelledby.
Pour aller plus loin
- ARIA Roles (MDN Web Docs), référence exhaustive avec exemples lien externe qui s'ouvre dans un nouvel onglet
- ARIA APG Patterns, comportements clavier par widget lien externe qui s'ouvre dans un nouvel onglet
- a11ysupport.io, support réel par AT et navigateur lien externe qui s'ouvre dans un nouvel onglet