Maîtriser la structure des tableaux
Formation DWWM - 2025-2026
C'est quoi un tableau ?
<tr><td> ou <th>
<!-- ❌ NE FAITES JAMAIS ÇA ! -->
<table>
<tr>
<td>HEADER</td>
</tr>
<tr>
<td>SIDEBAR</td>
<td>CONTENT</td>
</tr>
</table>
Les balises essentielles
<table>
<tr>
<th>Prénom</th>
<th>Âge</th>
</tr>
<tr>
<td>Alice</td>
<td>25</td>
</tr>
<tr>
<td>Bob</td>
<td>30</td>
</tr>
</table>
| Prénom | Âge |
|---|---|
| Alice | 25 |
| Bob | 30 |
<th> sont en gras par défaut<td> sont en texte normal
| Critère | <th> | <td> |
|---|---|---|
| Usage | En-tête de colonne/ligne | Données normales |
| Style par défaut | Gras + centré | Normal + aligné gauche |
| Sémantique | Définit une catégorie | Contient une valeur |
| Accessibilité | Lecteurs d'écran l'annoncent | Lu comme donnée |
<th> pour les en-têtes, pas juste pour du texte en gras !<table>
<tr>
<th>Nom</th>
<th>Prénom</th>
<th>Âge</th>
<th>Ville</th>
</tr>
<tr>
<td>Dupont</td>
<td>Alice</td>
<td>25</td>
<td>Paris</td>
</tr>
<tr>
<td>Martin</td>
<td>Bob</td>
<td>30</td>
<td>Lyon</td>
</tr>
</table>
| Nom | Prénom | Âge | Ville |
|---|---|---|---|
| Dupont | Alice | 25 | Paris |
| Martin | Bob | 30 | Lyon |
<!-- ✅ CORRECT : 3 cellules par ligne -->
<table>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</table>
<!-- ❌ INCORRECT : Nombre différent -->
<table>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>D</td>
<td>E</td> <!-- Il manque une cellule ! -->
</tr>
</table>
thead, tbody, tfoot
<table>
<thead>
<tr>
<th>Produit</th>
<th>Prix</th>
<th>Quantité</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pommes</td>
<td>2.50€</td>
<td>10</td>
</tr>
<tr>
<td>Bananes</td>
<td>1.80€</td>
<td>15</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Total</th>
<td>52€</td>
<td>25</td>
</tr>
</tfoot>
</table>
| Produit | Prix | Quantité |
|---|---|---|
| Pommes | 2.50€ | 10 |
| Bananes | 1.80€ | 15 |
| Total | 52€ | 25 |
/* Exemple de ciblage CSS */
thead {
background: #42affa;
color: white;
}
tbody tr:nth-child(even) {
background: rgba(255,255,255,0.05);
}
tfoot {
background: #90ee90;
font-weight: bold;
}
<tfoot> AVANT <tbody><!-- ✅ Ordre recommandé (logique) -->
<table>
<thead>...</thead>
<tbody>...</tbody>
<tfoot>...</tfoot>
</table>
<!-- ⚠️ Ordre HTML5 valide mais déroutant -->
<table>
<thead>...</thead>
<tfoot>...</tfoot> <!-- Avant tbody ! -->
<tbody>...</tbody>
</table>
colspan et rowspan
colspan="2" = La cellule prend 2 colonnes
<table>
<tr>
<th colspan="2">Nom complet</th>
<th>Âge</th>
</tr>
<tr>
<td>Alice</td>
<td>Dupont</td>
<td>25</td>
</tr>
</table>
| Nom complet | Âge | |
|---|---|---|
| Alice | Dupont | 25 |
rowspan="2" = La cellule prend 2 lignes
<table>
<tr>
<th rowspan="2">Nom</th>
<th>Téléphone</th>
</tr>
<tr>
<td>06 12 34 56 78</td>
</tr>
<tr>
<th rowspan="2">Bob</th>
<td>06 98 76 54 32</td>
</tr>
<tr>
<td>07 11 22 33 44</td>
</tr>
</table>
| Alice | 06 12 34 56 78 |
|---|---|
| 06 11 22 33 44 | |
| Bob | 06 98 76 54 32 |
| 07 11 22 33 44 |
<table>
<tr>
<th colspan="2" rowspan="2">Vide</th>
<th colspan="2">Lundi</th>
</tr>
<tr>
<th>Matin</th>
<th>Après-midi</th>
</tr>
<tr>
<th rowspan="2">Salle 1</th>
<th>8h-12h</th>
<td>Math</td>
<td>Français</td>
</tr>
<tr>
<th>13h-17h</th>
<td>Sport</td>
<td>Anglais</td>
</tr>
</table>
<!-- ❌ ERREUR : Trop de cellules -->
<table>
<tr>
<td colspan="2">A</td>
<td>B</td>
<td>C</td> <!-- UNE cellule de trop ! -->
</tr>
</table>
<!-- ✅ CORRECT : colspan="2" compte pour 2 cellules -->
<table>
<tr>
<td colspan="2">A</td> <!-- Prend 2 places -->
<td>B</td> <!-- 3ème place -->
</tr>
</table>
colspan="2"<td> dans la lignecaption, scope, headers
<table><table>
<caption>Liste des employés - Janvier 2025</caption>
<thead>
<tr>
<th>Nom</th>
<th>Poste</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Développeuse</td>
</tr>
</tbody>
</table>
<caption> pour les tableaux de donnéesscope="col" = En-tête de colonnescope="row" = En-tête de lignescope="colgroup" = Groupe de colonnesscope="rowgroup" = Groupe de lignes<table>
<tr>
<th scope="col">Nom</th>
<th scope="col">Âge</th>
</tr>
<tr>
<th scope="row">Alice</th>
<td>25</td>
</tr>
<tr>
<th scope="row">Bob</th>
<td>30</td>
</tr>
</table>
<table>
<tr>
<th id="nom">Nom</th>
<th id="lundi">Lundi</th>
<th id="mardi">Mardi</th>
</tr>
<tr>
<th id="alice" headers="nom">Alice</th>
<td headers="alice lundi">Présente</td>
<td headers="alice mardi">Absente</td>
</tr>
</table>
headers est rarement nécessairescope suffit dans 90% des cas
<caption> pour décrire le tableau<th> pour les en-têtesscope sur les <th><thead>, <tbody>headers si tableau complexe<!-- ✅ Tableau accessible complet -->
<table>
<caption>Horaires des cours - Semaine 1</caption>
<thead>
<tr>
<th scope="col">Jour</th>
<th scope="col">Matière</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Lundi</th>
<td>HTML</td>
</tr>
</tbody>
</table>
CSS pour les tableaux
collapse = Bordures fusionnées (1px entre cellules)separate = Bordures séparées (par défaut)
/* Bordures fusionnées (recommandé) */
table {
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
}
/* Bordures séparées */
table {
border-collapse: separate;
border-spacing: 5px; /* Espace entre cellules */
}
/* Style de base pour tableau */
table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
}
/* En-têtes */
th {
background: #42affa;
color: white;
padding: 12px;
text-align: left;
font-weight: bold;
}
/* Cellules */
td {
padding: 10px;
border: 1px solid #ddd;
}
/* Lignes alternées (zebra striping) */
tbody tr:nth-child(even) {
background: rgba(255, 255, 255, 0.05);
}
/* Hover */
tbody tr:hover {
background: rgba(66, 175, 250, 0.1);
}
| Nom | Prénom | Ville |
|---|---|---|
| Dupont | Alice | Paris |
| Martin | Bob | Lyon |
| Durand | Charlie | Marseille |
/* Largeur de colonnes */
th:nth-child(1) { width: 30%; }
th:nth-child(2) { width: 50%; }
th:nth-child(3) { width: 20%; }
/* Alignement du texte */
td {
text-align: left; /* Gauche par défaut */
}
td.prix {
text-align: right; /* Prix à droite */
}
td.centre {
text-align: center; /* Centré */
}
/* Débordement de texte */
td {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* En-tête qui reste visible au scroll */
table {
border-collapse: collapse;
}
thead {
position: sticky;
top: 0;
background: #42affa;
z-index: 10;
}
/* Ombre pour mieux voir */
thead::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
S'adapter au mobile
<div class="table-container">
<table>
<!-- Tableau normal -->
</table>
</div>
.table-container {
width: 100%;
overflow-x: auto; /* Scroll horizontal */
}
table {
min-width: 600px; /* Largeur minimale */
}
/* Sur mobile, masquer certaines colonnes */
@media (max-width: 768px) {
/* Masquer les colonnes moins importantes */
table td:nth-child(3),
table th:nth-child(3),
table td:nth-child(4),
table th:nth-child(4) {
display: none;
}
}
/* Indication visuelle */
@media (max-width: 768px) {
table::after {
content: '← Faites défiler pour voir plus →';
display: block;
text-align: center;
padding: 10px;
font-size: 0.8em;
}
}
/* Sur mobile : transformer en cartes */
@media (max-width: 768px) {
table, thead, tbody, th, td, tr {
display: block;
}
/* Masquer les en-têtes */
thead {
display: none;
}
/* Chaque ligne = une carte */
tbody tr {
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 10px;
}
/* Afficher le nom de la colonne avant la donnée */
td::before {
content: attr(data-label);
font-weight: bold;
display: block;
margin-bottom: 5px;
}
}
<table>
<thead>
<tr>
<th>Nom</th>
<th>Email</th>
<th>Téléphone</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Nom">Alice Dupont</td>
<td data-label="Email">alice@example.com</td>
<td data-label="Téléphone">06 12 34 56 78</td>
</tr>
</tbody>
</table>
👆 Rendu sur mobile
| Solution | Avantages | Inconvénients | Usage |
|---|---|---|---|
| Scroll horizontal | Simple, rapide | UX moyenne | Tableaux simples |
| Masquer colonnes | Garde la structure | Perte d'information | Colonnes optionnelles |
| Mode carte | Très lisible | Plus de code CSS | Tableaux complexes |
Pièges à éviter
<!-- ❌ MAUVAIS : Tableau pour le layout -->
<table>
<tr>
<td>Logo</td>
<td>Menu</td>
</tr>
<tr>
<td>Sidebar</td>
<td>Contenu</td>
</tr>
</table>
<!-- ✅ BON : Flexbox ou Grid -->
<div class="layout">
<header>Logo + Menu</header>
<main>
<aside>Sidebar</aside>
<article>Contenu</article>
</main>
</div>
<!-- ❌ ERREUR : 3 cellules puis 2 cellules -->
<table>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>D</td>
<td>E</td> <!-- Il manque une cellule ! -->
</tr>
</table>
<!-- Solution 1 : Ajouter la cellule manquante -->
<tr>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
<!-- Solution 2 : Cellule vide -->
<tr>
<td>D</td>
<td>E</td>
<td></td>
</tr>
<!-- ⚠️ Fonctionne mais pas sémantique -->
<table>
<tr>
<th>Nom</th>
<th>Âge</th>
</tr>
<tr>
<td>Alice</td>
<td>25</td>
</tr>
</table>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Âge</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>25</td>
</tr>
</tbody>
</table>
/* ❌ Bordures doublées */
table {
/* Pas de border-collapse */
}
th, td {
border: 1px solid black;
}
/* Résultat : 2px entre les cellules */
/* ✅ Bordures fusionnées */
table {
border-collapse: collapse;
}
th, td {
border: 1px solid black;
}
/* Résultat : 1px entre les cellules */
<!-- ❌ ERREUR : td dans thead -->
<thead>
<tr>
<td>Nom</td> <!-- Devrait être <th> ! -->
<td>Âge</td>
</tr>
</thead>
<!-- ❌ ERREUR : th dans tbody -->
<tbody>
<tr>
<th>Alice</th> <!-- Devrait être <td> ! -->
<th>25</th>
</tr>
</tbody>
<th> = En-têtes (colonnes/lignes)<td> = Données
Exemples réels
<table>
<caption>Tarifs des formations - 2025</caption>
<thead>
<tr>
<th scope="col">Formation</th>
<th scope="col">Durée</th>
<th scope="col">Prix</th>
</tr>
</thead>
<tbody>
<tr>
<td>DWWM</td>
<td>6 mois</td>
<td>8 000€</td>
</tr>
<tr>
<td>CDA</td>
<td>12 mois</td>
<td>12 000€</td>
</tr>
</tbody>
</table>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
border: 1px solid #ddd;
text-align: left;
}
th {
background: #42affa;
color: white;
}
td:nth-child(3) {
text-align: right;
font-weight: bold;
}
<table>
<caption>Planning - Semaine 3</caption>
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Lundi</th>
<th scope="col">Mardi</th>
<th scope="col">Mercredi</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">9h-12h</th>
<td>HTML</td>
<td>CSS</td>
<td>JavaScript</td>
</tr>
<tr>
<th scope="row">14h-17h</th>
<td>PHP</td>
<td>SQL</td>
<td>Projet</td>
</tr>
</tbody>
</table>
<table>
<caption>Comparaison des offres</caption>
<thead>
<tr>
<th scope="col">Fonctionnalité</th>
<th scope="col">Gratuit</th>
<th scope="col">Premium</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Stockage</th>
<td>5 Go</td>
<td>100 Go</td>
</tr>
<tr>
<th scope="row">Support</th>
<td>Email</td>
<td>24/7</td>
</tr>
<tr>
<th scope="row">Prix</th>
<td>0€</td>
<td>9.99€/mois</td>
</tr>
</tbody>
</table>
/* Mise en évidence de la colonne Premium */
th:nth-child(3),
td:nth-child(3) {
background: rgba(144, 238, 144, 0.1);
font-weight: bold;
}
<table>
<caption>Classement Ligue 1 - Journée 20</caption>
<thead>
<tr>
<th scope="col">Pos</th>
<th scope="col">Équipe</th>
<th scope="col">Pts</th>
<th scope="col">J</th>
<th scope="col">G</th>
<th scope="col">N</th>
<th scope="col">P</th>
</tr>
</thead>
<tbody>
<tr class="champion">
<td>1</td>
<td>PSG</td>
<td>50</td>
<td>20</td>
<td>16</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>2</td>
<td>Marseille</td>
<td>45</td>
<td>20</td>
<td>14</td>
<td>3</td>
<td>3</td>
</tr>
</tbody>
</table>
/* Zone Champions League */
tr.champion {
background: rgba(66, 175, 250, 0.2);
border-left: 4px solid #42affa;
}
<caption> ?thead/tbody ?<th> pour les en-têtes ?scope ?border-collapse: collapse ?table → tr → td/ththead, tbody, tfootcolspan (horizontal), rowspan (vertical)caption, scope, thborder-collapse: collapseVous maîtrisez les tableaux HTML ! 🚀
📊 Exercices pratiques :
1. Créez un tableau de planning
2. Rendez-le responsive
3. Ajoutez l'accessibilité complète !