Tables responsives

Les grands tableaux de données deviennent souvent illisibles sur les petits écrans, car les colonnes débordent ou rétrécissent au-delà de la lisibilité. Ce guide explique comment utiliser le positionnement fixe pour que les en-têtes restent visibles pendant le défilement et comment transformer le tableau en une mise en page "empilée" adaptée aux mobiles lorsque l'espace est limité.

L'essentiel d'un tableau responsif est de maintenir la relation entre les cellules de données et leurs en-têtes.

  1. Fondation sémantique : utilisez des éléments <table> standards avec des éléments <thead>, <tbody> et <th>.
  2. Contexte persistant : appliquez position: sticky aux en-têtes de colonne et de ligne. Cela garantit que, quelle que soit la distance parcourue par un utilisateur dans n'importe quelle direction, il ne perd jamais le contexte de ce que représentent les données.
  3. Transformations adaptatives : utilisez des requêtes @container au lieu de requêtes @media. Cela permet au tableau de s'adapter en fonction de sa propre largeur (par exemple, lorsqu'il est placé dans une barre latérale ou un widget de tableau de bord étroit) plutôt que de l'ensemble de la fenêtre d'affichage.
  4. Injection d'étiquettes : dans la mise en page empilée, l'élément <thead> est masqué, et des en-têtes accessibles sont injectés dans chaque cellule à l'aide de pseudo-éléments ::before et de variables CSS.

Mise en œuvre, étape par étape

1. Balisage et mise en page

Structurez votre tableau avec des en-têtes sémantiques standards. Utilisez un .table-wrapper pour gérer le dépassement et fournir un conteneur pour les requêtes. Ajoutez un attribut tabindex="0" au conteneur pour vous assurer que les personnes qui n'utilisent qu'un clavier peuvent faire défiler le tableau de manière cohérente.

Attribuez un attribut role="region" au wrapper pour indiquer son objectif. Donnez-lui un nom accessible à l'aide de aria-labelledby (en référençant l'attribut id d'un élément existant qui légende/nomme le tableau) ou d'un attribut aria-label.

<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
  <table>
    <caption id="employee-table-caption">Current employee directory</caption>
    <thead>
      <tr>
        <th>Employee</th>
        <th>Role</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <!-- Row header remains sticky horizontally on desktop, 
             and becomes a sticky card header on mobile -->
        <th>Alex Rivera</th>
        <td>Engineer</td>
        <td>Active</td>
      </tr>
    </tbody>
  </table>
</div>

2. Configurer les en-têtes persistants

Activez le défilement et figez les en-têtes. Utilisez des propriétés logiques et des valeurs z-index explicites pour gérer l'ordre d'empilement.

.table-wrapper {
  overflow: auto;
  max-inline-size: 100%;
  max-block-size: min(500px, 80vh);
  container-type: inline-size;
}

table {
  border-collapse: separate;
  border-spacing: 0;
}

/* Sticky column headers (desktop) */
thead {
  position: sticky;
  inset-block-start: 0;
  z-index: 1; /* Above sticky row headers */
}

/* Sticky row headers (desktop) */
tbody th {
  position: sticky;
  inset-inline-start: 0;
  background: white; /* Required to cover background during scroll */
}

3. Mise en page empilée responsive

Lorsque l'espace est limité, masquez la ligne d'en-tête d'origine et transformez le tableau en fiches. Injectez des libellés à l'aide de pseudo-éléments. Ne fournissez un texte alternatif que pour le contenu non textuel, sinon les lecteurs d'écran risquent d'annoncer à la fois le contenu textuel et le nom accessible.

@container (width < 600px) {
  table, thead, tbody, tr, th, td {
    display: block;
  }

  thead {
    /* MANDATORY: Hide column header from layout and screen readers. */
    display: none
  }

  tr {
    margin-block-end: 1.5rem;
    border: 1px solid #ccc;
  }

  /* Sticky row headers at the top of each block/card */
  tbody th {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    background: #eee;
  }

  /* Define column labels as CSS variables (often set via JS) */
  table {
    --label-1: "Employee";
    --label-2: "Role";
    --label-3: "Status";
  }

  /* Inject labels */
  td:nth-child(2)::before {
    /* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
    content: var(--label-2) ": ";
  }
  /* MANDATORY: Map the label for each column */
  td:nth-child(3)::before {
    content: var(--label-3) ": ";
  }
}