Aller au contenu

Composants accessibles

Le tableau complexe

Un tableau à deux niveaux d'en-têtes se lit à l'aveugle dans une seule direction : celle que le code lui donne. Le tenir demande quatre règles ; le scinder ou l'aplatir n'en demande aucune. Démo, code, critères RGAA.

4 critères RGAA en jeu

Essayez-les

1. Le tableau complexe, tenu

Critères par thématique et par page auditée Les en-têtes de colonne sont sur deux niveaux : la page auditée, puis pour chacune le nombre de critères non conformes et conformes. Une ligne par thématique ; pas de ligne de total.
AccueilContact
Non conformesConformesNon conformesConformes
Images2709
Liens1414

2. Scindé : un tableau simple par page

Page Accueil — critères par thématique
Non conformesConformes
Images27
Liens14
Page Contact — critères par thématique
Non conformesConformes
Images09
Liens14

3. Aplati : la page devient une colonne

Critères par page et par thématique
PageThématiqueNon conformesConformes
AccueilImages27
AccueilLiens14
ContactImages09
ContactLiens14

Les trois grilles portent la même donnée. Ouvrez le mode tableau de votre lecteur d’écran et parcourez la première cellule par cellule : chaque valeur doit s’annoncer avec trois en-têtes — la thématique, la page, puis la colonne — « Images, Accueil, Non conformes, 2 ». Passez ensuite aux deux suivantes : deux en-têtes suffisent, et rien ne manque. C’est tout le propos de la fiche.

Cette démo existe aussi en page autonome.

L’erreur courante

L’en-tête à deux niveaux déclaré avec scope="colgroup", et une cellule d’en-tête vide dans le coin.

<!-- À ne pas faire : le scope ne couvre pas une colonne entière, et le coin est un th -->
<table>
	<tr>
		<th rowspan="2"></th>
		<th colspan="2" scope="colgroup">Accueil</th>
		<th colspan="2" scope="colgroup">Contact</th>
	</tr>
	<tr>
		<th scope="col">Non conformes</th>
		<th scope="col">Conformes</th>
		<th scope="col">Non conformes</th>
		<th scope="col">Conformes</th>
	</tr>
	<tr>
		<th scope="row">Images</th>
		<td>2</td><td>7</td><td>0</td><td>9</td>
	</tr>
</table>

C’est le code que les guides WCAG enseignent, et il n’est pas absurde : colgroup existe pour cela. Mais le RGAA lit autrement. Son test 5.7.3 dit qu’un <th> qui ne s’applique pas à la totalité d’une ligne ou d’une colonne ne porte pas d’attribut scope, et porte un id unique. « Accueil » chapeaute deux colonnes sans en couvrir aucune en entier : il est dans ce cas. Un audit RGAA marque donc ce tableau non conforme au 5.7, tout scope qu’il ait.

Le <th> vide du coin est une erreur plus discrète. Sur Safari et Chrome avec VoiceOver, cette cellule d’en-tête sans contenu entre dans les associations et les brouille : des cellules s’annoncent avec un en-tête muet, ou avec le mauvais. Le coin est une case vide, donc une donnée — un <td>.

Enfin, ce tableau fusionne dans les deux sens à la fois : un rowspan dans le coin, des colspan sur la première ligne. C’est la combinaison que VoiceOver restitue le plus mal, en associant des cellules à des en-têtes qui ne sont ni sur leur ligne ni sur leur colonne.

Comment le coder

1. Décider s’il est complexe

Le glossaire du référentiel tranche : un tableau est complexe quand ses en-têtes ne tiennent pas sur la seule première ligne et la seule première colonne, ou quand un en-tête ne s’applique qu’à une partie de sa ligne ou de sa colonne. Deux niveaux d’en-têtes de colonne, c’est complexe. Une double entrée avec un seul niveau de chaque côté ne l’est pas — scope y suffit, et la fiche tableaux de données s’en occupe.

Des cellules de données fusionnées ne rendent pas un tableau complexe. Elles posent un autre problème, traité au point 6.

2. Un seul axe de fusion

Si les en-têtes de colonne ont deux niveaux, les en-têtes de ligne n’en ont qu’un. Si les lignes sont regroupées, les colonnes ne le sont pas. Tenir les deux à la fois est possible en HTML et illisible en pratique : c’est le cas que la matrice de tests de l’article cité en source a vu échouer sur le plus de lecteurs.

<thead>
	<tr>
		<td rowspan="2"></td>
		<th colspan="2" id="p-accueil">Accueil</th>
		<th colspan="2" id="p-contact">Contact</th>
	</tr>
	<tr>
		<th id="a-nc">Non conformes</th>
		<th id="a-ok">Conformes</th>
		<th id="c-nc">Non conformes</th>
		<th id="c-ok">Conformes</th>
	</tr>
</thead>

Le rowspan du coin ne compte pas comme une fusion d’en-tête : c’est un <td> vide qui occupe la place, et aucune cellule ne le désigne.

3. id sur chaque en-tête, headers sur chaque cellule

Sans scope, tout passe par headers. Chaque cellule de données liste les id de tous ses en-têtes, du plus général au plus précis : la thématique, la page, la colonne.

<tr>
	<th id="t-images">Images</th>
	<td headers="t-images p-accueil a-nc">2</td>
	<td headers="t-images p-accueil a-ok">7</td>
	<td headers="t-images p-contact c-nc">0</td>
	<td headers="t-images p-contact c-ok">9</td>
</tr>

La technique se tient sur tout le tableau : dès qu’un en-tête est lié par id, les en-têtes de ligne le sont aussi, sans scope="row". Mélanger les deux techniques dans une grille donne des restitutions différentes selon le lecteur.

Un id doit être unique dans la page, pas seulement dans le tableau. Deux tableaux qui reprennent p-accueil se volent leurs en-têtes.

4. Le coin en <td>

La cellule vide au croisement des en-têtes de ligne et de colonne est une donnée absente, pas un en-tête. Elle reste un <td>, sans id, et rien ne la désigne dans un headers.

5. Le résumé de structure

Le tableau complexe doit un résumé (critère 5.1). En HTML5, la seule technique du référentiel qui tienne encore est de l’écrire dans la caption, à la suite du titre — l’attribut summary est obsolète. Le résumé décrit comment lire : le nombre de niveaux, ce que chacun représente, ce qui n’y est pas.

<caption>
	<span id="titre-complexe">Critères par thématique et par page auditée</span>
	<span class="resume">
		Les en-têtes de colonne sont sur deux niveaux : la page auditée, puis
		pour chacune le nombre de critères non conformes et conformes. Une ligne
		par thématique ; pas de ligne de total.
	</span>
</caption>

Un résumé qui redit le titre, ou qui décrit les chiffres, ne dit rien de la structure : présent, il tombe au 5.2.

6. Scinder plutôt que fusionner

Avant d’écrire un seul headers, poser la question : ce tableau doit-il être un ? Un tableau à deux niveaux de colonnes est presque toujours plusieurs tableaux simples juxtaposés — un par page, ici. Chacun se déclare avec scope, aucun ne doit de résumé, et un lecteur d’écran y navigue sans surprise.

<table>
	<caption>Page Accueil — critères par thématique</caption>
	<thead>
		<tr><td></td><th scope="col">Non conformes</th><th scope="col">Conformes</th></tr>
	</thead>
	<tbody>
		<tr><th scope="row">Images</th><td>2</td><td>7</td></tr>
	</tbody>
</table>

Même raisonnement pour les tableaux stratifiés — ceux où une ligne d’en-tête fusionnée sur toute la largeur ouvre une section (« Trimestre 1 », puis « Trimestre 2 ») : une section est un tableau, ou la strate devient une colonne.

7. Aplatir : la strate devient une colonne

L’autre sortie garde un seul tableau et lui rend sa simplicité. Ce qui était un niveau d’en-tête devient une colonne de données ; chaque ligne porte alors sa page et sa thématique, et la grille n’a plus qu’un niveau de chaque côté.

<thead>
	<tr>
		<th scope="col">Page</th>
		<th scope="col">Thématique</th>
		<th scope="col">Non conformes</th>
		<th scope="col">Conformes</th>
	</tr>
</thead>
<tbody>
	<tr><td>Accueil</td><td>Images</td><td>2</td><td>7</td></tr>
	<tr><td>Contact</td><td>Images</td><td>0</td><td>9</td></tr>
</tbody>

On y perd une chose : la valeur « Accueil » se répète. C’est le prix, et il est bas — la répétition se lit, la fusion se devine. Le même principe vaut pour les cellules de données fusionnées : la plupart des lecteurs d’écran les sautent en parcourant la ligne, et NVDA leur annonce le mauvais en-tête de ligne. Répéter la valeur est presque toujours préférable.

Au clavier

Un tableau complexe n’est pas interactif ; sa navigation appartient au lecteur d’écran. La différence avec un tableau simple tient à ce qui est annoncé à chaque déplacement, pas aux touches.

Touches du clavier et leur effet sur un tableau complexe
ToucheEffet
Ctrl + Alt + flèchesDéplace de cellule en cellule (NVDA, JAWS) ; à chaque changement de colonne, les deux niveaux d’en-tête sont annoncés.
Ctrl + Alt + DébutRetourne à la première cellule du tableau — utile quand la lecture s’est perdue dans une fusion.
TVa au tableau suivant (mode navigation rapide) ; sur la version scindée, c’est ainsi qu’on passe d’une page à l’autre.
TabNe s’arrête que sur la zone de défilement, quand il y en a une, et sur les liens ou boutons que les cellules contiennent.

Ce que le lecteur d’écran restitue

Sur le tableau tenu (la première grille), chaque cellule s’annonce avec ses trois en-têtes : « Images, Accueil, Non conformes, 2 ». Sur les deux autres, avec deux. Si un niveau manque à l’annonce, l’association est rompue quelque part — un headers qui oublie un id, ou un scope là où il n’a plus cours.

La restitution varie beaucoup plus d’un lecteur à l’autre que sur un tableau simple, et c’est mesuré. Une matrice de tests publiée fin 2025 sur neuf couples navigateur/lecteur d’écran donne les limites du terrain : TalkBack n’a restitué les en-têtes de colonne dans aucune configuration ; VoiceOver sur iOS gère mal le second niveau ; sur Mac, il associe de travers dès que les fusions portent sur les deux axes ; NVDA parcourt les cellules de données fusionnées mieux que les autres, mais avec des en-têtes de ligne inattendus. La conclusion de l’auteur tient en une phrase : faire simple, ou prévoir de tester largement.

L’extension RGAA Lab relève ces pièges dans le code — coin en <th>, colonne sans en-tête, fusions sur les deux axes, en-têtes de ligne répétés — et les montre à côté de la question du critère 5.7, sans conclure à votre place. Le scope sur un en-tête fusionné, lui, est une non-conformité lue directement : c’est le test 5.7.3.

Checklist

  • Un seul axe de fusion : deux niveaux de colonnes ou des lignes regroupées, jamais les deux.
  • id unique sur chaque en-tête, headers complet sur chaque cellule, aucun scope — la technique se tient sur tout le tableau.
  • Le coin vide est un <td>, sans id, désigné par personne.
  • Un résumé dans la caption, qui décrit les niveaux et ce qui manque, pas le titre.
  • Des en-têtes de ligne uniques : deux lignes « Total » se confondent à l’oreille.
  • Avant tout cela : scinder en tableaux simples, ou aplatir la strate en colonne, si la donnée le permet.
  • Pas de cellule de données fusionnée quand répéter la valeur suffit.

Les critères RGAA en jeu

Vérifiez votre composant avec l'extension

Pour aller plus loin

Sources