Maîtriser les tableaux HTML et leur stylisation CSS pour présenter des données structurées de manière professionnelle.
<table>) sert à présenter des données tabulaires — des informations organisées en lignes et colonnes.
Les tableaux sont l'un des éléments les plus anciens du HTML. Contrairement aux listes (qui sont linéaires) ou aux grilles CSS (qui sont des mises en page), un tableau est conçu pour des données structurées : comparaisons, classements, emplois du temps, matrices de données.
<table>
<caption>Titre du tableau</caption>
<thead>
<tr>
<th>En-tête 1</th>
<th>En-tête 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Donnée 1</td>
<td>Donnée 2</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Pied 1</td>
<td>Pied 2</td>
</tr>
</tfoot>
</table>
Anatomie complète d'un tableau HTML sémantique
| Balise | Rôle | Description |
|---|---|---|
<table> |
Conteneur | Élément racine du tableau |
<caption> |
Titre | Titre sémantique du tableau (accessibilité) |
<thead> |
En-tête | Groupe de lignes d'en-tête |
<tbody> |
Corps | Groupe de lignes de données principales |
<tfoot> |
Pied | Groupe de lignes de résumé/pied |
<tr> |
Ligne | Une ligne du tableau |
<th> |
Cellule d'en-tête | Cellule d'en-tête (gras + centré par défaut) |
<td> |
Cellule | Cellule de données standard |
| Propriété | Valeurs | Effet |
|---|---|---|
border-collapse |
separate | collapse |
Fusionne ou sépare les bordures des cellules |
border-spacing |
Valeurs de longueur | Espace entre les cellules (avec separate) |
table-layout |
auto | fixed |
Algorithme de calcul de la largeur des colonnes |
vertical-align |
top | middle | bottom |
Alignement vertical du contenu dans les cellules |
text-align |
left | center | right |
Alignement horizontal du texte dans les cellules |
width |
Pourcentage, px, auto | Largeur du tableau |
La propriété border-collapse contrôle la fusion des bordures entre les cellules adjacentes :
/* Par défaut : les bordures sont séparées */
table {
border-collapse: separate;
border-spacing: 8px; /* espace entre cellules */
}
/* Bordures fusionnées (le plus courant) */
table {
border-collapse: collapse;
}
/* Calcul automatique basé sur le contenu (lent) */
table {
table-layout: auto;
}
/* Largeur fixe basée sur la première ligne (rapide) */
table {
table-layout: fixed;
width: 100%;
}
table-layout: fixed est beaucoup plus rapide pour les grands tableaux car le navigateur n'a pas besoin d'analyser tout le contenu pour calculer les largeurs. Les colonnes prennent une largeur proportionnelle égale (sauf si spécifié avec width).
Comparaison visuelle : separate (gauche) garde l'espacement entre cellules, collapse (droite) fusionne les bordures
| Valeur | Rendu |
|---|---|
top |
Texte aligné en haut de la cellule. Ceci est un texte assez long pour montrer l'alignement vertical. |
middle |
Texte centré verticalement dans la cellule. |
bottom |
Texte aligné en bas de la cellule. |
<table class="table-base">
<thead>
<tr>
<th>Langage</th>
<th>Année</th>
<th>Paradigme</th>
</tr>
</thead>
<tbody>
<tr>
<td>JavaScript</td>
<td>1995</td>
<td>Multi-paradigme</td>
</tr>
<tr>
<td>Python</td>
<td>1991</td>
<td>Orienté objet</td>
</tr>
</tbody>
</table>
Rendu :
| Langage | Année | Paradigme | Popularity |
|---|---|---|---|
| JavaScript | 1995 | Multi-paradigme | ⭐⭐⭐⭐⭐ |
| Python | 1991 | Orienté objet | ⭐⭐⭐⭐⭐ |
| TypeScript | 2012 | Multi-paradigme | ⭐⭐⭐⭐ |
| Rust | 2010 | Système / Fonctionnel | ⭐⭐⭐ |
| Go | 2009 | Impératif | ⭐⭐⭐⭐ |
/* Zébrage des lignes paires */
.table-zebra tbody tr:nth-child(even) {
background-color: #f1f5f9;
}
.table-zebra tbody tr:nth-child(odd) {
background-color: #ffffff;
}
/* Effet de survol */
.table-zebra tbody tr:hover {
background-color: #e0f2fe;
}
Rendu :
| # | Nom | Rôle | Statut | |
|---|---|---|---|---|
| 001 | Alice Martin | alice@example.com | Développeuse | Actif |
| 002 | Bob Dupont | bob@example.com | Designer | Actif |
| 003 | Claire Leroy | claire@example.com | Chef de projet | En attente |
| 004 | David Moreau | david@example.com | Développeur | Inactif |
| 005 | Emma Bernard | emma@example.com | DevOps | Premium |
| 006 | François Petit | francois@example.com | Développeur | Actif |
.table-scroll-wrapper {
max-height: 300px;
overflow-y: auto;
}
.table-sticky thead th {
position: sticky;
top: 0;
background-color: #2563eb;
color: white;
z-index: 1;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
Rendu :
| ID | Nom du projet | Statut | Progression |
|---|---|---|---|
| PRJ-001 | Refonte site e-commerce | En cours | 78% |
| PRJ-002 | Application mobile iOS | En attente | 12% |
| PRJ-003 | Migration base de données | En cours | 95% |
| PRJ-004 | Dashboard analytics | En cours | 45% |
| PRJ-005 | API REST v2 | Terminé | 100% |
| PRJ-006 | Système de notifications | En attente | 0% |
| PRJ-007 | Tests d'intégration | En cours | 60% |
| PRJ-008 | Documentation API | En cours | 33% |
| PRJ-009 | Mise en production | Planifié | 0% |
| PRJ-010 | Audit de sécurité | En cours | 88% |
/* Sur mobile, chaque ligne devient une "carte" */
@media (max-width: 600px) {
.table-block table,
.table-block thead,
.table-block tbody,
.table-block tr,
.table-block th,
.table-block td {
display: block;
}
/* Cache les en-têtes */
.table-block thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.table-block td {
display: flex;
justify-content: space-between;
padding: 0.5rem 1rem;
}
/* Utilise data-label pour le nom de la colonne */
.table-block td::before {
content: attr(data-label);
font-weight: 600;
color: var(--couleur-texte-clair);
}
}
Rendu :
| Service | Prix/mois | Utilisateurs | Statut |
|---|---|---|---|
| Essentiel | 9,99 € | 1 | Actif |
| Professionnel | 29,99 € | 10 | Actif |
| Entreprise | 79,99 € | Illimité | En attente |
Rendu :
| Employé | Département | Localisation | Statut | Performance |
|---|---|---|---|---|
|
AM
Alice Martin
|
Ingénierie | Paris | En poste | ⭐⭐⭐⭐⭐ |
|
BD
Bob Dupont
|
Design | Lyon | En poste | ⭐⭐⭐⭐ |
|
CL
Claire Leroy
|
Marketing | Marseille | En mission | ⭐⭐⭐⭐ |
|
DM
David Moreau
|
Support | Toulouse | Congé | ⭐⭐⭐ |
.table-sort th {
cursor: pointer;
user-select: none;
position: relative;
padding-right: 2rem;
}
.table-sort th .sort-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
}
.table-sort th.sorted-asc .sort-icon::after {
content: "▲"; /* Flèche tri ascendant */
}
.table-sort th.sorted-desc .sort-icon::after {
content: "▼"; /* Flèche tri descendant */
}
Rendu :
| Nom | Population | Superficie | Densité |
|---|---|---|---|
| Paris | 2 161 000 | 105,4 km² | 20 503 hab/km² |
| Marseille | 873 000 | 240,6 km² | 3 628 hab/km² |
| Lyon | 522 000 | 47,8 km² | 10 920 hab/km² |
| Toulouse | 498 000 | 118,3 km² | 4 210 hab/km² |
| Nice | 340 000 | 71,9 km² | 4 729 hab/km² |
Rendu :
| Commande | Client | Montant | Date | Statut |
|---|---|---|---|---|
| #CMD-20451 | Sophie Durand | 249,00 € | 15/07/2026 | Livrée |
| #CMD-20452 | Marc Laurent | 89,50 € | 16/07/2026 | En cours |
| #CMD-20453 | Julie Rousseau | 412,00 € | 16/07/2026 | En préparation |
| #CMD-20454 | Thomas Girard | 67,80 € | 17/07/2026 | Annulée |
| #CMD-20455 | Camille Bonnet | 156,25 € | 17/07/2026 | VIP |
Rendu :
| Gratuit | Pro ★ | Entreprise | |
|---|---|---|---|
| Prix | 0 € | 19 €/mois | 79 €/mois |
| Espace stockage | 1 Go | 100 Go | Illimité |
| Utilisateurs | 1 | 10 | Illimité |
| Support | Communauté | 24/7 Téléphone | |
| Analytics | Basique | Avancé | |
| API | REST | REST + GraphQL | |
| SLA | 99,9% |
Rendu :
| Lu | Ma | Me | Je | Ve | Sa | Di |
|---|---|---|---|---|---|---|
| 29 | 30 | 1 | 2 | 3 | 4 | 5 |
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | 31 | 1 | 2 |
<table class="table-fixed">
<colgroup>
<col class="col-narrow">
<col class="col-wide">
<col class="col-medium">
</colgroup>
...
</table>
Rendu :
| ID | Description | Catégorie |
|---|---|---|
| #A1 | Bandeau de navigation responsive avec menu hamburger | Composant UI |
| #B2 | Formulaire de contact avec validation côté client | Formulaire |
| #C3 | Grille de produits e-commerce avec filtres | Layout |
Les tableaux de mise en page (display: table sur des divs non tabulaires) sont une pratique obsolète. Utilisez Flexbox ou CSS Grid à la place.
/* ❌ MAUVAIS — Tableau de mise en page */
<table>
<tr>
<td>Logo</td>
<td>Menu</td>
<td>Recherche</td>
</tr>
</table>
/* ✅ BON — Flexbox pour la mise en page */
<header style="display: flex; align-items: center;">
<div class="logo">...</div>
<nav class="menu">...</nav>
<div class="recherche">...</div>
</header>
Ces éléments sémantiques sont essentiels pour l'accessibilité et permettent le positionnement sticky des en-têtes.
/* ❌ MAUVAIS — Sans structure sémantique */
<table>
<tr><th>Nom</th><th>Âge</th></tr>
<tr><td>Alice</td><td>25</td></tr>
</table>
/* ✅ BON — Avec structure complète */
<table>
<thead>
<tr><th>Nom</th><th>Âge</th></tr>
</thead>
<tbody>
<tr><td>Alice</td><td>25</td></tr>
</tbody>
</table>
Le <caption> fournit un titre accessible au tableau. Les technologies d'assistance ( lecteurs d'écran ) l'utilisent pour annoncer le contenu du tableau.
L'attribut HTML border="1" est déprécié. Utilisez toujours CSS pour les bordures.
/* ❌ MAUVAIS */
<table border="1">
/* ✅ BON */
<table>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #ccc; }
</style>
</table>
Un large tableau sur petit écran déborde et casse la mise en page. Utilisez overflow-x: auto ou convertissez le tableau en carte.
/* ✅ Solution 1 : Défilement horizontal */
.table-responsive-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* ✅ Solution 2 : Conversion en blocs sur mobile */
@media (max-width: 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead { /* Accessibilité : masquer visuellement */ }
td::before { content: attr(data-label); }
}
Quand border-collapse: collapse est actif, border-radius ne fonctionne pas sur les <td> et <th>. Il faut utiliser separate pour obtenir des coins arrondis.
C'est le style le plus courant et le plus propre. Les bordures fusionnées donnent un aspect professionnel.
Même si le rendu visuel est identique, ces éléments sont essentiels pour l'accessibilité et permettent des styles ciblés (sticky header, zébrage limité au tbody...).
Chaque tableau doit avoir un titre. C'est une exigence WCAG pour l'accessibilité.
Le zébrage améliore la lisibilité des longs tableaux. Appliquez-le sur <tbody> pour éviter de zébrer l'en-tête.
Si vos colonnes ont des largeurs connues, fixed est beaucoup plus rapide car le navigateur n'a pas besoin de parcourir tout le contenu.
Pour les tableaux complexes, les attributs HTML scope et headers aident les lecteurs d'écran à associer les données aux en-têtes.
<th scope="col">Nom</th> <!-- Colonne entière -->
<th scope="row">Total</th> <!-- Ligne entière -->
Tout tableau doit être consultable sur mobile. Soit par défilement horizontal contrôlé, soit par conversion en layout carte.
Les cellules comprimées sont illisibles. Un minimum de 0.75rem 1rem est recommandé.
Défi n°1 — Tableau de scores
Objectif : Créer un tableau de classement de joueurs avec :
<tbody>Défi n°2 — Tableau de comparaison de produits
Objectif : Créer un tableau comparatif avec :
table-layout: fixed<caption> pour l'accessibilitéDéfi n°3 — Mini calendrier mensuel
Objectif : Créer un calendrier avec :
Consigne :
Créez un tableau de bord de données complet qui combine les concepts vus dans ce chapitre :
<caption> et des attributs scope pour l'accessibilité| Concept | Propriété / Élément | Utilisation |
|---|---|---|
| Structure | <table>, <thead>, <tbody>, <tfoot>, <tr>, <th>, <td>, <caption> |
Anatomie sémantique du tableau |
| Fusion des bordures | border-collapse |
collapse (fusionné) ou separate (espacé) |
| Espacement | border-spacing |
Espace entre cellules (avec separate) |
| Algorithme de layout | table-layout |
auto (calculé) ou fixed (première ligne) |
| Alignement vertical | vertical-align |
top, middle, bottom dans les cellules |
| Zébrage | :nth-child(even/odd) |
Lignes alternées pour la lisibilité |
| En-tête fixe | position: sticky |
En-tête qui reste visible au défilement |
| Responsive | overflow-x: auto ou conversion display: block |
Adaptation mobile des tableaux larges |
| Accessibilité | <caption>, scope |
Titre et liaisons en-tête-cellule |
| Largeur colonnes | <col>, <colgroup> |
Contrôle de la largeur sans affecter le contenu |
border-radius de fonctionner sur les cellules ?table-layout: auto et fixed ?<tbody> plutôt que sur le tableau entier ?border-collapse: collapse<caption>auto calcule selon le contenu (lent), fixed selon la première ligne (rapide)clip: rect(0,0,0,0) et position: absolute (clip-path ou sr-only)/* Tableau de base */
table {
width: 100%;
border-collapse: collapse;
}
/* Zébrage */
tbody tr:nth-child(even) {
background-color: #f1f5f9;
}
/* En-tête sticky */
thead th {
position: sticky;
top: 0;
background: #2563eb;
z-index: 1;
}
/* Wrapper responsive */
.table-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Conversion mobile */
@media (max-width: 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
td::before { content: attr(data-label); font-weight: 600; }
}