156 outils gratuits de conversion en ligne
Texte-Word 13 min de lecture

Comment faire un tableau HTML avec des en-têtes figés en 5 étapes

Faire un tableau HTML demande une poignée de balises, et chacun y parvient du premier coup. Le garder lisible avec cinquante lignes, c’est une autre histoire. On fixe sa hauteur, une barre de défilement surgit, puis il suffit de descendre de trois crans pour que la ligne de titres parte avec le reste. Sur deux colonnes, on devine encore. Huit colonnes de chiffres, en revanche, et on ne sait plus ce qu’on lit.

Ce tutoriel reprend, pas à pas, une méthode qui règle le problème avec du CSS et une seule petite règle JavaScript. À la fin, vous aurez un tableau dont les titres restent collés en haut, parfaitement alignés sur leurs colonnes, pendant que les données défilent dessous. Le tout sans bibliothèque de composants, sans plugin, et avec un code que vous écrivez une fois pour tous vos tableaux.

Les points essentiels à retenir

  • Ce n’est pas au bloc qui affiche les titres de porter le défilement : il faut un conteneur intérieur autour de la table.
  • En haut, le bloc extérieur réserve une bande avec un padding-top égal à la hauteur de la ligne de titres, à savoir 45 px dans la démonstration.
  • On sort les titres du flux : chaque th reçoit une div, et c’est cette div qu’on passe en position absolute avec top 0.
  • Deux lignes de JavaScript recopient la largeur de chaque th dans sa div, sans quoi les titres ne tombent pas en face des colonnes.
  • Ultime réglage : avec une table large de 99,9 % plutôt que 100 %, la barre de défilement en trop disparaît.

Le point de départ : un tableau qui défile et perd ses titres

Au départ, la structure tient sur deux niveaux. Une div porte la classe du tableau ; dedans, une table des plus classiques : une ligne de th pour les titres, ensuite des lignes de td pour les données.

<div class="container_12 tableau">
  <table>
    <tr><th>Pays</th><th>Nombre de vues</th></tr>
    <tr><td>France</td><td>124807</td></tr>
    <tr><td>Maroc</td><td>43961</td></tr>
  </table>
</div>

En CSS, on donne à la div une hauteur fixe et un overflow automatique. Ce duo fait apparaître la barre de défilement :

.tableau { font-size: 24px; height: 200px; margin: auto; overflow: auto; width: 450px; }
.tableau table { margin: 0; width: 100%; }
.tableau th { background: black; border: black 1px solid; color: white; padding: 10px; }
.tableau table td { background: #8C8C8C; border: black 1px solid; color: black; }

Ça fonctionne, sauf que la ligne noire des titres appartient à la zone qui défile. Comme n’importe quelle autre ligne, elle file vers le haut. Tout l’enjeu sera de l’extraire de cette zone sans casser l’alignement des colonnes.

Code HTML d'un tableau avec une ligne de th Pays et Nombre de vues suivie des lignes de données
Le point de départ : une div autour de la table, une ligne de titres, des lignes de données.

Étape 1 : deux conteneurs plutôt qu’un, et une bande réservée en haut

Une phrase suffit pour l’idée : en haut, le bloc extérieur garde une marge intérieure vide, et les titres viendront s’afficher dans cette bande. Quant aux données, elles défilent dans un second conteneur placé entre la div de départ et la table.

  1. Placez une div autour de la table, par exemple avec la classe container_tab.
  2. Transférez la hauteur et l’overflow vers cette nouvelle div : le défilement lui revient, et non au bloc extérieur.
  3. Attribuez au bloc extérieur un position: relative afin que tout ce qu’on positionnera par la suite se cale sur son coin supérieur gauche, pas sur celui de la page.
  4. Donnez-lui un padding-top égal à la hauteur exacte de votre ligne de titres.

Méfiez-vous du quatrième point : c’est là que l’erreur arrive le plus vite. Ici, une marge extérieure ne sert à rien, puisque la bande doit appartenir au bloc ; il faut donc bien un padding. Sa valeur, elle, ne se devine pas. Dans la démonstration, un premier essai à 20 px laisse les titres empiéter sur la première ligne de données, puis l’inspecteur du navigateur indique 45 px pour la ligne de titres, et c’est cette valeur qu’on retient.

.tableau { font-size: 24px; margin: auto; padding-top: 45px; position: relative; width: 450px; }
.tableau .container_tab { height: 200px; overflow: auto; }
Feuille CSS montrant padding-top 45px et position relative sur le bloc extérieur, height et overflow sur container_tab
La hauteur et l’overflow passent sur le conteneur intérieur, le bloc extérieur garde la bande du haut.
La démonstration complète en vidéo, étape par étape.

Étape 2 : une div dans chaque cellule d’en-tête

Première tentation : passer directement les th en position: absolute avec un top: 0. Essayez, vous verrez le résultat tout de suite. Les titres se placent bien dans la bande, mais ils perdent leur taille, puisqu’un élément sorti du flux ne reçoit plus la largeur que la table lui attribuait. Les deux intitulés se retrouvent collés l’un à l’autre, à mille lieues de leurs colonnes.

Pour s’en sortir, on ne touche pas au th lui-même. On place une div dans chaque cellule d’en-tête, puis c’est elle qu’on décolle :

<tr><th><div>Pays</div></th><th><div>Nombre de vues</div></th></tr>
.tableau th > div { position: absolute; top: 0; }

Pourquoi est-ce que ça marche ? Parce que ces div se positionnent par rapport au bloc extérieur, celui qui porte le position: relative, et ce bloc ne défile pas. Les lignes glissent sous les titres sans jamais les emmener. Autre effet secondaire bienvenu : les th n’ont plus rien dans leur flux, donc la zone de défilement démarre directement sur la première ligne de données.

Ligne HTML où chaque cellule th contient une div autour du texte Pays et Nombre de vues
Chaque th reçoit une div : c’est elle, et non la cellule, qui passera en position absolute.

Étape 3 : la ligne de JavaScript qui remet les titres en face des colonnes

Il reste un défaut : la div absolue ne connaît toujours pas la largeur de sa colonne. On pense évidemment à width: inherit. Mauvaise pioche. L’héritage ne fonctionne que si le th porte une largeur explicite, par exemple 50 %. Dès qu’il est en automatique, la div hérite d’une valeur automatique, autrement dit de rien du tout. Et demander à chacun de figer la largeur de toutes ses colonnes pour faire un tableau HTML correct, ce serait un bien mauvais service.

D’où ce bref passage par le JavaScript, qui tient en deux lignes grâce à jQuery :

<script type="text/javascript">
$(".tableau th").each(function(){
    $(this).find("div").css("width", $(this).width())
});
</script>

La boucle passe sur chaque cellule d’en-tête, récupère la div qu’elle contient, et lui applique la largeur mesurée du th. Même idée en JavaScript natif, si jQuery n’est pas chargé sur votre page :

document.querySelectorAll(".tableau th").forEach(function (th) {
  th.querySelector("div").style.width = th.getBoundingClientRect().width + "px";
});

On écrit cette règle une seule fois. Chaque tableau de votre site portant la classe en bénéficie, peu importe son nombre de colonnes.

Script jQuery parcourant les th du tableau pour appliquer leur largeur à la div enfant
Deux lignes suffisent : pour chaque th, la largeur mesurée est recopiée dans sa div.

Étape 4 : les finitions qui font la différence

Si le mécanisme tient debout, le rendu réclame encore quatre réglages. C’est grâce à eux qu’un tableau propre se distingue d’un bricolage visible.

Le fond déménage sur le conteneur

Tant que personne ne la colore, la bande réservée reste blanche, d’où un bandeau clair au-dessus d’un tableau sombre. Pour corriger, on retire le fond des titres et on le pose sur le bloc extérieur, qui couvre toute la largeur. Avec un en-tête au fond plus travaillé, une image de la hauteur de la ligne, répétée sur l’axe horizontal, fait la même chose.

Le padding, oui, mais vertical seulement

Le padding latéral des titres décale les div par rapport à leurs colonnes. On le retire sur les côtés et on garde celui du haut et du bas, qui aère le texte.

La bordure disparaît

Le léger décalage qu’on voit entre un titre et sa colonne vient de la bordure. Comme le fond est désormais uni, elle ne sert plus à rien sur les titres : on la supprime et l’alignement devient net.

99,9 % plutôt que 100 %

Une barre de défilement surnuméraire apparaît quand la table occupe 100 % de la largeur. Passer à 99,9 % la fait disparaître sans que l’œil ne voie la différence.

Feuille CSS avec background noir sur le bloc tableau et la règle th div en position absolute
Le fond noir quitte les titres pour le conteneur extérieur, qui remplit toute la bande.

Le code complet du tableau à en-têtes figés

Voici l’ensemble, prêt à être recopié et adapté. Le HTML :

<div class="container_12 tableau">
  <div class="container_tab">
    <table>
      <tr><th><div>Pays</div></th><th><div>Nombre de vues</div></th></tr>
      <tr><td>France</td><td>124807</td></tr>
      <tr><td>Maroc</td><td>43961</td></tr>
    </table>
  </div>
</div>

Le CSS :

.tableau { background: black; font-size: 24px; margin: auto; padding-top: 45px; position: relative; width: 450px; }
.tableau .container_tab { height: 200px; overflow: auto; }
.tableau table { margin: 0; width: 99.9%; }
.tableau th > div { background: black; color: white; padding: 10px 0; position: absolute; top: 0; }
.tableau table td { background: #8C8C8C; border: black 1px solid; color: black; }

Largeur, hauteur et padding reprennent les valeurs de l’exemple. Chez vous, elles dépendront de votre police et de votre mise en page : c’est le seul endroit où l’on doit mesurer avant de copier.

Tableau HTML dont la ligne de titres Pays et Nombre de vues reste visible alors que les lignes du bas ont défilé
Les titres tiennent en place pendant que les lignes défilent sous eux.

Les erreurs fréquentes avec les en-têtes fixes

  • Laisser le défilement sur le bloc extérieur. Tant que la div qui porte les titres garde la hauteur et l’overflow, ces derniers s’en vont avec les données. Ce réglage doit descendre d’un niveau.
  • Utiliser une marge au lieu d’un padding. Une marge pousse le bloc entier vers le bas, elle ne réserve aucun espace à l’intérieur. Les titres viennent alors se superposer à la première ligne.
  • Positionner les th en absolute. La cellule perd alors sa taille, et l’alignement part en vrille. C’est la div intérieure qu’on doit décoller.
  • Compter sur width: inherit. Elle n’agit que si la largeur du parent est écrite en dur. Sur des colonnes automatiques, rien ne se passe.
  • Choisir le padding-top au jugé. Dans l’inspecteur, mesurez la hauteur réelle de la ligne de titres et reportez la valeur obtenue. Trop faible, les titres mordent sur les données ; trop grande, un vide apparaît.
  • Oublier le fond de la bande. Sans couleur, la zone réservée reste blanche et trahit le montage au premier coup d’œil.

Questions fréquentes

Peut-on se passer complètement de JavaScript ?

Dans cette méthode, non, et c’est assumé. Tout le reste est du CSS, le script se contente de recopier une largeur. L’obstacle est précis : un enfant ne peut pas récupérer automatiquement la largeur de son parent quand celle-ci n’a pas été fixée. Si vous acceptez de déclarer la largeur de vos colonnes en pourcentage ou en pixels, le width: inherit redevient envisageable.

Est-ce que ça fonctionne avec plus de deux colonnes ?

Oui, et c’est même dans ce cas que la technique prend tout son sens. Comme la boucle parcourt chaque th, chaque titre obtient la largeur de sa propre colonne. Plus le tableau est large et chargé de chiffres, plus des en-têtes figés transforment la lecture.

Que se passe-t-il si la largeur du tableau change après le chargement ?

Le script recopie la largeur au moment où il s’exécute. Si la table change de dimensions après coup, par exemple parce que la fenêtre est redimensionnée ou que des lignes arrivent plus tard, on relance la même boucle pour replacer les titres face aux colonnes.

Et si mon tableau vient d’Excel ou de Word ?

Le plus simple : obtenir d’abord un code propre, ensuite appliquer la méthode décrite plus haut. Un convertisseur Excel vers HTML vous fournit la table balisée ; il ne reste qu’à y ajouter les deux conteneurs et les div dans les th. Depuis un document texte, la logique est la même avec le convertisseur Word vers HTML.

Votre tableau existe déjà dans un tableur ?

Collez-le et récupérez un code HTML propre, prêt à recevoir vos en-têtes figés.

Essayer gratuitement

Quiz : avez-vous tout retenu ?

1. Quel élément doit porter la hauteur et l’overflow ?



2. À quoi sert le padding-top du bloc extérieur ?



3. Pourquoi glisser une div dans chaque th ?



4. Que fait la boucle JavaScript ?



5. Pourquoi passer la table à 99,9 % de largeur ?



Désormais, vous savez faire un tableau HTML dont les titres ne s’échappent plus. Au passage, vous avez constaté que la bonne solution vient rarement du premier coup : on tente une piste, elle casse, on comprend pourquoi, puis on ajuste. Ce width: inherit qui refuse de coopérer en est la meilleure illustration. Pour des tableaux qui doivent aussi tenir sur un écran de téléphone, la suite logique est du côté des tableaux HTML responsives sans framework, où le problème de lecture se pose autrement.

Un mail par semaine, et rien d'autre

Chaque lundi, je vous envoie les guides publiés dans la semaine : un cas concret, les captures qui vont avec, et l'astuce qui fait gagner dix minutes. Si ça ne vous sert pas, un clic suffit pour partir.