Composant · Données et contenus riches

Role grid ou table : la grille interactive accessible RGAA

Role grid ou simple tableau HTML ? Table de décision, roving tabindex, clavier en deux dimensions et critères RGAA d'une grille interactive accessible.

Avant d'écrire role="grid", une seule question compte : l'utilisateur lit-il des données, ou les manipule-t-il cellule par cellule ? Toute l'accessibilité du motif se joue dans cette réponse.

<table> seul<table role="grid">
UsageLire des donnéesNaviguer et interagir cellule par cellule
ClavierTouches de lecture habituellesLes flèches déplacent le focus de cellule en cellule
Mode lecteur d'écranMode lectureMode application
ExemplesListe de prix, rapportTableur, tableau de données éditable

Une grille (grid) est donc un widget composite tabulaire, navigable et opérable aux flèches : le motif d'un tableur ou d'un tableau éditable. La bonne base reste un vrai <table> avec ses <th scope>, auquel role="grid" ajoute le modèle d'interaction sans jeter les associations d'en-têtes. Une grille reconstruite en <div> perd ces associations et doit tout rebâtir, en moins bien.

Quand ce motif est le mauvais choix

C'est la première décision, et l'erreur la plus fréquente autour de ce motif :

  • Tableau en lecture seule (liste de prix, rapport) : un <table> ordinaire, sans role="grid". Ajouter role="grid" à un tableau statique est une dégradation : les lecteurs d'écran basculent en mode application et l'utilisateur doit apprendre une navigation aux flèches pour un contenu que ses touches de lecture géraient très bien. Aucun outil automatique ne signale ce défaut, car le balisage est techniquement valide.
  • Lignes dépliables mais cellules en lecture seule : un <table> avec des boutons porteurs d'aria-expanded suffit, pas de grille.
  • Liste d'options sélectionnables sans structure tabulaire : c'est un listbox, pas une grille. La grille devient le bon motif quand les options contiennent des actions à opérer.
  • Plusieurs bibliothèques de tableaux posent role="grid" par défaut : le retirer si le tableau est en lecture seule. role="grid" se justifie quand les cellules contiennent des contrôles à opérer, ou quand la navigation et la sélection cellule par cellule sont le cœur de l'usage.

Critères RGAA applicables

Interaction clavier attendue

ToucheAction
Flèches droite / gauche / bas / hautDéplacent le focus d'une cellule dans la direction correspondante
Début (Home) / Fin (End)Première / dernière cellule de la ligne courante
Ctrl + Début / Ctrl + FinPremière cellule de la première ligne / dernière cellule de la dernière ligne
Page haut / Page basSaut de plusieurs lignes
Entrée ou F2Entre dans la cellule pour utiliser le contrôle qu'elle contient (mode édition)
ÉchapQuitte le mode édition et revient à la navigation de grille
TabSort de la grille (jamais capturé)

Rôles et attributs ARIA

role="grid" se pose sur le <table>, avec un nom accessible : aria-labelledby pointant le <caption>, ou aria-label. Les rôles row et gridcell sont implicites, conservés par la structure de tableau. En option : aria-sort sur un en-tête trié, aria-selected pour la sélection, aria-readonly sur les cellules non éditables. aria-colcount et aria-rowcount ne servent qu'en cas de virtualisation : si toutes les lignes sont dans le DOM, le navigateur compte mieux qu'un attribut maintenu à la main.

<!-- Un vrai <table> : role="grid" ajoute l'interaction SANS jeter
     la sémantique des en-têtes (critères 5.6 et 5.7). -->
<table role="grid" aria-labelledby="budget-caption">
  <caption id="budget-caption">Budget T3 par service (éditable)</caption>
  <thead>
    <tr>
      <th scope="col">Service</th>
      <th scope="col">Budget</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <!-- Les en-têtes de ligne restent des <th scope="row"> :
           le rôle grid n'exonère pas de la thématique 5. -->
      <th scope="row" tabindex="0">Ingénierie</th>
      <!-- Roving tabindex : une seule cellule à tabindex="0",
           toutes les autres à -1. -->
      <td tabindex="-1">
        <input type="text" value="120000" tabindex="-1"
               aria-label="Budget Ingénierie">
      </td>
    </tr>
  </tbody>
</table>

Le champ interne est en tabindex="-1" : la cellule est la cible de tabulation, et le champ ne devient utilisable qu'après Entrée. Cette bascule de mode empêche les flèches du champ texte et celles de la grille de se disputer les mêmes touches.

// La bascule de mode, côté script : en édition, la grille rend la main.
grille.addEventListener('keydown', (event) => {
  if (enEdition) {
    if (event.key === 'Escape') {
      enEdition = false;
      cellule.focus(); // retour à la navigation de grille
    }
    return; // flèches, Début, Fin : tout appartient au champ en édition
  }
  // ... navigation aux flèches entre cellules ...
  // Tab : jamais géré. C'est la seule sortie de la grille (critère 12.9).
});

Dernier point de câblage : le focus doit se déplacer réellement (appel à .focus()), pas via aria-activedescendant, pour que l'indication visuelle de focus suive la cellule active (critère 10.7).

Et les deux contre-exemples qui résument les dérives du motif :

<!-- À NE PAS FAIRE : role="grid" sur un tableau en lecture seule.
     Mode application imposé pour un contenu qui se lisait très bien. -->
<table role="grid">
  <caption>Chiffre d'affaires par région</caption>
  <tr><th scope="col">Région</th><td>2,4 M€</td></tr>
</table>

<!-- À NE PAS FAIRE : une grille en <div>.
     Plus d'associations d'en-têtes (critères 5.6 et 5.7) : tout ce que
     <table> donnait doit être rebâti à la main, en moins bien. -->
<div role="grid">
  <div role="row"><div role="gridcell">Ingénierie</div></div>
</div>

Défauts fréquents et impact utilisateur

  • role="grid" sur un tableau en lecture seule : le défaut numéro un. Mode application imposé, navigation aux flèches obligatoire : un tableau lisible devient plus difficile à lire pour l'utilisateur de lecteur d'écran.
  • Tab intercepté par la grille : la grille possède déjà toutes les flèches ; si Tab est aussi capturé, l'utilisateur au clavier est enfermé sans aucune sortie, la pire forme de piège au clavier (critère 12.9).
  • Contrôles internes tabbables sans bascule de mode : la cellule et son champ sont deux arrêts de tabulation, et une flèche dans le champ déplace à la fois le curseur texte et la cellule focalisée ; positionner son curseur devient impossible.
  • Toutes les cellules en tabindex="0" : une grille de 20 lignes sur 10 colonnes devient 200 arrêts de tabulation ; traverser la page au clavier prend des minutes (critère 12.8).
  • En-têtes abandonnés « parce que c'est une grille » : sans scope="row", une cellule est annoncée sans le service auquel elle appartient (critères 5.6 et 5.7).
  • aria-rowcount maintenu à la main sur une grille entièrement rendue : après un filtre, la grille annonce « ligne 3 sur 50 » dans une grille de 3 lignes.

Ce que les outils automatiques ne détectent pas

  • Un role="grid" posé sur un tableau en lecture seule : le choix du motif est une décision d'usage, pas de syntaxe.
  • Un Tab capturé (piège au clavier) ou des flèches qui se disputent une cellule en mode édition : seuls des tests au clavier le révèlent.
  • La cohérence dynamique du roving tabindex et des états aria-selected / aria-sort avec l'affichage.

Les outils automatiques, dont notre scanner, repèrent en revanche une grille sans nom accessible ou une structure de rôles invalide (enfants directs de grid qui ne sont pas des row) : un filet utile avant les vérifications manuelles qui suivent.

Vérifier ce composant

Protocole manuel, dans l'ordre où les défauts sont les plus probables :

  1. La question préalable : quelque chose dans les cellules est-il interactif ? Sinon, retirer role="grid" et laisser vivre le tableau. C'est le défaut le plus courant.
  2. Le test Tab (critère 12.9) : Tab pour entrer dans la grille, Tab à nouveau. On doit être sorti. Un seul arrêt de tabulation pour toute la grille.
  3. Le test du mode édition : entrer dans une cellule à champ texte (Entrée), puis Flèche gauche. Le curseur texte doit bouger, pas la cellule focalisée. Échap doit rendre la main à la grille.
  4. Le test des en-têtes : naviguer aux flèches vers une cellule du milieu ; le lecteur d'écran doit encore annoncer ses en-têtes de ligne et de colonne.

Deux outils gratuits du site aident sur ce composant : le Simulateur Lecteur d'Écran pour visualiser l'arbre d'accessibilité (rôles grid, row, gridcell, nom accessible, en-têtes), et le Testeur Focus Visible pour contrôler que la cellule active reste repérable au fil de la navigation (critère 10.7).

Approfondir avec les fiches critères

Vérifier ce composant sur votre site ?

Le scan repère les défauts détectables automatiquement (rôles incohérents, champs sans étiquette, attributs ARIA orphelins) ; le reste se vérifie à la main avec les protocoles de cette fiche.

Lancer un scan gratuit

Toutes les fiches composants accessibles