Pour chaque tableau de données, la technique appropriée permettant d’associer chaque cellule avec ses en-têtes est-elle utilisée (hors cas particuliers) ?
Un lecteur d’écran parcourt un tableau cellule par cellule. À chaque cellule de données, il annonce les en-têtes qui s’y rapportent : « Lundi, 10h, Réunion d’équipe ». Pour que cette annonce fonctionne, chaque cellule doit être techniquement liée à ses en-têtes. Si cette association est absente ou incorrecte, l’utilisateur entend les données brutes sans contexte : « Réunion d’équipe », sans savoir quand ni quel jour.
Pour un tableau simple (une seule rangée d’en-têtes, une seule colonne d’en-têtes), l’attribut scope sur les <th> suffit. scope="col" pour les en-têtes de colonne, scope="row" pour les en-têtes de ligne. Les navigateurs et lecteurs d’écran déduisent l’association automatiquement. Pas besoin d’id ni d’headers dans ce cas.
Pour un tableau complexe (plusieurs niveaux d’en-têtes, cellules fusionnées, en-têtes qui ne couvrent qu’une partie de la colonne), il faut aller plus loin : chaque <th> partiel reçoit un id unique, et chaque cellule de données porte un attribut headers listant les identifiants de tous ses en-têtes. C’est verbeux, mais c’est la seule technique fiable pour les tableaux imbriqués.
Alternative ARIA : role="columnheader" et role="rowheader" peuvent remplacer les <th> avec scope, mais uniquement pour les en-têtes qui couvrent toute la ligne ou colonne. Cette technique ne remplace pas le mécanisme id / headers pour les tableaux complexes.
5 tests pour contrôler le lien entre cellules et en-têtes de tableau
Attribut id, scope ou rôle ARIA sur les <th> complets
Pour chaque tableau de données du document, repérez les <th> qui couvrent toute une ligne ou toute une colonne.
Pour chacun de ces <th>, vérifiez qu’il possède au moins l’un de ces attributs :
- Un attribut
idavec une valeur unique dans la page ; - Un attribut
scope(quelle que soit sa valeur) ; - Un attribut
roleavec la valeur"rowheader"ou"columnheader".
Si tous les <th> de ce type possèdent au moins l’un de ces attributs : test validé. Si l’un d’eux n’en a aucun : test échoué.
Valeur correcte de l'attribut scope sur les <th>
Ce test ne porte que sur les <th> qui couvrent toute une ligne ou colonne ET qui utilisent déjà l’attribut scope.
Pour chacun de ces <th>, vérifiez la valeur de scope :
scope="row"si le<th>est un en-tête de ligne ;scope="col"si le<th>est un en-tête de colonne.
Une valeur incorrecte (scope="rowgroup" utilisé à tort, ou une valeur inventée) fait échouer le test. Si tous les scope ont la bonne valeur : test validé.
Attribut id unique sur les <th> partiels
Ce test cible les <th> partiels : des en-têtes qui ne couvrent qu’une partie de la ligne ou de la colonne (tableaux complexes avec en-têtes fusionnés ou hiérarchiques).
Pour chacun de ces <th> partiels, vérifiez les trois conditions simultanément :
- Il possède un attribut
idunique dans la page ; - Il ne possède PAS d’attribut
scope; - Il ne possède PAS de
role="rowheader"nirole="columnheader".
Toutes les conditions doivent être vraies. Si l’une manque (pas d’id, ou présence de scope, ou présence d’un role de header) : test échoué.
Attribut headers sur les cellules liées à des <th> avec id
Ce test s’applique aux tableaux où des <th> portent des attributs id (donc les tableaux complexes vérifiés en 5.7.3).
Pour chaque cellule <td> ou <th> qui est associée à un ou plusieurs de ces en-têtes :
- Vérifiez que la cellule possède un attribut
headers; - Vérifiez que cet attribut
headersliste exactement les valeursidde tous ses en-têtes, séparées par des espaces.
Un headers incomplet (un id manquant) ou incorrect (un id qui pointe vers le mauvais en-tête) fait échouer le test. Si toutes les cellules ont un headers complet et exact : test validé.
Rôle ARIA correct sur les en-têtes de tableau
Ce test concerne uniquement les en-têtes qui couvrent toute une ligne ou colonne ET qui utilisent un rôle ARIA à la place du scope.
Pour chacun de ces éléments portant role="rowheader" ou role="columnheader" :
role="rowheader"doit être utilisé pour un en-tête de ligne ;role="columnheader"doit être utilisé pour un en-tête de colonne.
Un role="columnheader" posé sur un en-tête de ligne (erreur d’inversion) fait échouer le test. Si chaque rôle correspond bien à son orientation : test validé.
Exemples
❌ Non conforme : Tableau simple sans association des en-têtes
<table>
<caption>Résultats par trimestre</caption>
<thead>
<tr>
<th>Produit</th>
<th>T1</th>
<th>T2</th>
<th>T3</th>
</tr>
</thead>
<tbody>
<tr>
<th>Widget A</th>
<td>1 200</td>
<td>1 450</td>
<td>980</td>
</tr>
<tr>
<th>Widget B</th>
<td>870</td>
<td>920</td>
<td>1 100</td>
</tr>
</tbody>
</table>Aucun scope sur les <th>. NVDA annonce « 980 » sans préciser que c’est le chiffre de Widget A au T3. L’utilisateur doit remonter manuellement dans les en-têtes pour reconstituer le contexte : une contrainte cognitive élevée dans les tableaux larges.
✅ Conforme : Tableau simple avec scope correctement renseigné
<table>
<caption>Résultats par trimestre</caption>
<thead>
<tr>
<th scope="col">Produit</th>
<th scope="col">T1</th>
<th scope="col">T2</th>
<th scope="col">T3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Widget A</th>
<td>1 200</td>
<td>1 450</td>
<td>980</td>
</tr>
<tr>
<th scope="row">Widget B</th>
<td>870</td>
<td>920</td>
<td>1 100</td>
</tr>
</tbody>
</table>Chaque <th> déclare sa portée. NVDA annonce « Widget A T3 : 980 ». L’association est calculée automatiquement par le navigateur. Le développeur n’a pas besoin de id ni de headers pour ce tableau à deux dimensions simples.
✅ Conforme : Tableau complexe avec id et headers
<table>
<caption>Répartition des notes par matière et période</caption>
<thead>
<tr>
<th id="mat" scope="col">Matière</th>
<th id="sem1" colspan="2" scope="colgroup">Semestre 1</th>
<th id="sem2" colspan="2" scope="colgroup">Semestre 2</th>
</tr>
<tr>
<th id="s1-cc" headers="sem1">Contrôle continu</th>
<th id="s1-ex" headers="sem1">Examen</th>
<th id="s2-cc" headers="sem2">Contrôle continu</th>
<th id="s2-ex" headers="sem2">Examen</th>
</tr>
</thead>
<tbody>
<tr>
<th id="math" headers="mat" scope="row">Mathématiques</th>
<td headers="math s1-cc sem1">14</td>
<td headers="math s1-ex sem1">16</td>
<td headers="math s2-cc sem2">13</td>
<td headers="math s2-ex sem2">17</td>
</tr>
</tbody>
</table>Les en-têtes du deuxième niveau (contrôle continu, examen) ne couvrent pas toute la colonne : ils portent des id sans scope. Chaque cellule liste dans headers tous ses en-têtes ascendants. NVDA annonce « Mathématiques Semestre 1 Examen : 16 », chaîne complète de contexte.
❌ Non conforme : Tableau complexe avec association id/headers incorrecte (F90)
<table>
<caption>Résultats examens</caption>
<thead>
<tr>
<th id="e">Écrits</th>
<th id="p">Pratiques</th>
</tr>
<tr>
<th id="e1" headers="e">Note 1</th>
<th id="e2" headers="e">Note 2</th>
<th id="p1" headers="p">Note 1</th>
<th id="p2" headers="p">Note 2</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="e p1">15</td>
<td headers="e p2">12</td>
<td headers="e e1">10</td>
<td headers="e e2">14</td>
</tr>
</tbody>
</table>Les valeurs headers sont inversées : la première cellule pointe vers e et p1 alors qu’elle devrait pointer vers e et e1. C’est exactement le cas d’échec F90 : l’association id/headers est fausse. NVDA annonce « Pratiques Note 1 : 15 » pour une donnée de la colonne Écrits. Ce type d’erreur survient fréquemment lors de copier-coller de lignes sans mise à jour des attributs.
Astuces et pièges
⚠️ scope sur les <th> partiels : une erreur qui invalide tout
Dans un tableau complexe, poser scope="col" sur un <th> qui ne couvre qu’une partie de la colonne induit les lecteurs d’écran en erreur. Le test 5.7.3 interdit explicitement l’usage de scope (et des rôles ARIA de header) sur ces <th> partiels : ils doivent uniquement porter un id. C’est l’erreur la plus fréquente dans les tableaux à deux niveaux d’en-têtes.
⚠️ Copier-coller de lignes sans mettre à jour headers
La technique F90 décrit précisément ce scénario : on duplique une ligne <tbody>, on oublie de corriger les valeurs headers, et les associations pointent vers les mauvais en-têtes. En audit, vérifiez systématiquement que chaque headers référence les bons id — pas seulement que l’attribut existe.
💡 Pour 90 % des tableaux, scope suffit
Les tableaux vraiment complexes (plusieurs niveaux d’en-têtes, cellules fusionnées couvrant des sous-ensembles) sont rares en pratique. Un tableau avec une rangée d’en-têtes de colonnes et une colonne d’en-têtes de lignes est résolu avec scope="col" et scope="row". Inutile de déployer le mécanisme id / headers sur des tableaux simples : c’est plus lourd à maintenir et source d’erreurs.
⚠️ scope="rowgroup" et scope="colgroup" pour les en-têtes groupés
Quand un <th> couvre plusieurs colonnes avec colspan (ex. : en-tête « Semestre 1 » couvrant deux colonnes), la valeur correcte est scope="colgroup". Le test 5.7.2 n’attend que "row" ou "col" pour les en-têtes simples, mais "rowgroup" et "colgroup" sont également valides pour les en-têtes qui couvrent des groupes. Le RGAA ne les mentionne pas explicitement dans les tests, mais leur usage correct est conforme.
⚠️ Cas particulier : tableaux de mise en forme
Le critère 5.7 ne s’applique qu’aux tableaux de données. Un tableau de mise en forme (qui ne devrait idéalement pas exister, mais reste présent dans des codebases legacy) avec role="presentation" est hors périmètre. Assurez-vous d’abord de distinguer les deux catégories (critère 5.3).
💡 ARIA rowheader / columnheader : uniquement pour les en-têtes complets
Les rôles role="rowheader" et role="columnheader" sont une alternative valide à scope, mais uniquement pour les en-têtes qui couvrent toute la ligne ou colonne. Ils ne remplacent pas le mécanisme id / headers pour les en-têtes partiels. Le test 5.7.3 interdit d’ailleurs leur usage sur les <th> partiels.
Questions fréquentes
Quelle différence existe-t-il entre scope et id/headers pour associer les cellules d'un tableau RGAA ?
scope pour les tableaux dont chaque en-tête couvre une ligne ou une colonne entière. id/headers dès qu’un en-tête ne couvre qu’une partie de la ligne ou de la colonne, ou quand les en-têtes sont sur plusieurs niveaux imbriqués. En pratique : si votre tableau contient des colspan ou rowspan sur des <th>, pensez id/headers.
Pourquoi un <th> sans attribut d'association est-il non conforme selon le RGAA ?
Pas systématiquement. Le RGAA prévoit une exception : pour les tableaux dont les en-têtes se trouvent sur une seule ligne ou une seule colonne, les <th> sans attribut sont acceptés. Un tableau statistique classique avec une ligne de titres en première ligne passe le test 5.7.1 sans aucun scope.
Comment auditer efficacement le critère RGAA 5.7 sur l'association cellules/en-têtes ?
Activez NVDA avec Firefox ou JAWS avec Chrome et naviguez dans le tableau cellule par cellule (touches fléchées en mode tableau). Pour chaque cellule de données, le lecteur d’écran doit annoncer les en-têtes pertinents dans le bon ordre. Inspectez aussi le DOM : chaque id de <th> doit être référencé dans exactement les bonnes cellules via headers, et aucune valeur ne doit être dupliquée ou manquante.
Quand et comment utiliser scope="rowgroup" et scope="colgroup" selon le RGAA ?
Le test 5.7.2 ne liste que "row" et "col" comme valeurs valides pour les en-têtes couvrant une ligne ou colonne entière. rowgroup et colgroup ne sont pas couverts par les tests RGAA et leur support par les lecteurs d’écran reste inégal selon les combinaisons. Évitez-les : pour les structures nécessitant une association par groupe, utilisez id/headers.
Quelle est la règle RGAA pour un <th> partiel combinant à la fois id et scope ?
Non. Le test 5.7.3 l’interdit explicitement : un en-tête partiel doit avoir un id unique, mais ne doit pas avoir scope ni role="rowheader"/"columnheader". La présence de scope sur un en-tête partiel trompe le lecteur d’écran sur le périmètre de cet en-tête. L’association correcte passe uniquement par l’attribut headers des cellules concernées.