CSS Grid : créer une mise en page moderne pas à pas

Une page web complète, c’est rarement une seule rangée de blocs : c’est un en-tête, un menu latéral, une zone de contenu et un pied de page, qui doivent s’aligner dans les deux sens. CSS Grid est l’outil conçu exactement pour ça, et c’est le guide promis à la fin de l’article sur Flexbox. Nous allons construire ce gabarit de page pas à pas, du premier display: grid jusqu’à la galerie responsive sans media query.

Comme d’habitude sur ce site, tous les exemples ont été exécutés dans Chrome 151 pendant la rédaction. Quand j’écris qu’une colonne mesure 268 px ou qu’un bloc atterrit à 216 px du bord, ce sont des valeurs relevées dans le navigateur, pas des chiffres théoriques. Copiez les extraits et vérifiez chez vous.

Grid ou Flexbox : deux outils, pas un duel

La différence tient en une phrase : Flexbox aligne des éléments dans une seule direction à la fois (une ligne ou une colonne), Grid contrôle les lignes et les colonnes en même temps. Pour une barre de navigation, Flexbox reste parfait. Pour le squelette d’une page ou une grille de cartes qui doit s’aligner dans les deux sens, Grid est plus simple et plus prévisible. Les deux se combinent très bien : Grid pour le gabarit, Flexbox à l’intérieur des zones.

Prérequis pour suivre : savoir lier une feuille de style et écrire un sélecteur, comme vu dans l’article donner du style à une page grâce au CSS. Tout le reste s’explique en chemin.

Étape 1 : déclarer la grille et ses colonnes

Tout commence comme pour Flexbox, par une déclaration sur le conteneur :

<div class="grille">
  <div>Un</div>
  <div>Deux</div>
  <div>Trois</div>
</div>
.grille {
  display: grid;
}

Premier constat mesuré, et première surprise : contrairement à display: flex, rien ne se met côte à côte. Mes trois div restent empilées et occupent chacune toute la largeur du conteneur (600 px dans mon test). C’est normal : une grille sans colonnes déclarées n’a qu’une seule colonne. La propriété qui donne sa forme à la grille, c’est grid-template-columns :

.grille {
  display: grid;
  grid-template-columns: 200px 1fr 100px;
}

Chaque valeur crée une colonne. Ici : une colonne fixe de 200 px, une colonne en 1fr, une colonne fixe de 100 px. L’unité fr (fraction) signifie « une part de l’espace restant ». Mesure dans mon conteneur de 600 px : les blocs se placent à 0, 200 et 500 px, et la colonne du milieu reçoit exactement 300 px, c’est-à-dire tout ce qui reste après les deux colonnes fixes. C’est le motif classique « menu, contenu, marge » obtenu en une ligne de CSS.

Le fr sert aussi à doser des colonnes proportionnelles entre elles :

.grille {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
}

Le conteneur de 600 px est découpé en quatre parts : mes trois colonnes mesurent 150, 300 et 150 px. La colonne centrale, à 2fr, reçoit deux parts. Aucun pourcentage à calculer, et si la largeur du conteneur change, la proportion tient toute seule.

Étape 2 : gap, et les lignes qui se créent toutes seules

Comme en Flexbox, gap pose des gouttières entre les cellules, sans marge à bricoler sur le premier ou le dernier élément :

.grille {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
}

repeat(2, 1fr) est un raccourci pour 1fr 1fr. Mesure avec cinq blocs dans 600 px : chaque colonne fait 292 px, soit la moitié de l’espace restant une fois la gouttière de 16 px déduite. Et voici la vraie découverte de cette étape : je n’ai déclaré que des colonnes, jamais de lignes. Les cinq blocs se rangent pourtant tout seuls, deux par deux, et le cinquième démarre une troisième ligne (mesuré à 112 px du haut : deux rangées de 40 px et deux gouttières de 16 px). Ces lignes créées à la demande s’appellent les lignes implicites : la grille grandit en hauteur au fil du contenu, exactement ce qu’on attend d’une liste de cartes.

Un chiffre à retenir au passage : dans l’exemple de l’étape 1, la colonne 1fr mesurait 300 px. En ajoutant gap: 16px à cette même grille, elle tombe à 268 px. Les deux gouttières (32 px en tout) se paient sur l’espace restant, donc sur les colonnes en fr, jamais sur les colonnes fixes. Si vos comptes ne tombent pas juste, pensez aussi à vérifier padding et bordures avec le modèle de la boîte.

Étape 3 : placer un élément où on veut

Jusqu’ici, les blocs se rangent dans l’ordre du HTML. La vraie puissance de Grid, c’est de pouvoir placer un élément précisément. Pour ça, il faut savoir que le navigateur numérote les lignes de séparation de la grille : une grille de 3 colonnes a 4 lignes verticales, numérotées de 1 à 4.

1 2 3 4 1 2 3 grid-column: 1 / 3 cellule rangée 2
Les numéros désignent les lignes de séparation, pas les cellules : « de la ligne 1 à la ligne 3 » couvre deux colonnes.

Ces numéros s’utilisent avec grid-column et grid-row, au format début / fin :

.large {
  grid-column: 1 / 3; /* de la ligne 1 à la ligne 3 : 2 colonnes */
}

.haut {
  grid-column: 3;
  grid-row: 1 / 3; /* 2 rangées */
}

Mesures dans une grille de 3 colonnes (colonnes de 189,33 px, gap de 16 px) : le bloc .large s’étale sur 394,66 px, soit deux colonnes plus la gouttière qui les sépare. Le bloc .haut occupe 96 px de hauteur, soit deux rangées de 40 px plus la gouttière. Les autres blocs se replacent automatiquement dans les cellules restantes.

Une mésaventure de rédaction qui vaut la peine d’être racontée : à mon premier essai, le bloc .haut refusait de s’étirer et plafonnait à 40 px. La cause : une height: 40px posée sur tous les blocs de mon banc de test. Un élément qui a une hauteur fixe garde cette hauteur, même si sa zone de grille fait deux rangées ; c’est l’étirement par défaut (la valeur stretch) qui remplit la zone, et une taille explicite le désactive. Une fois la hauteur fixe retirée, le bloc est passé de 40 à 96 px. Si un élément semble ignorer sa zone, cherchez d’abord une taille fixe qui traîne.

Étape 4 : le gabarit de page avec grid-template-areas

Les numéros de lignes fonctionnent, mais pour un gabarit complet il existe une écriture beaucoup plus lisible : nommer des zones et les dessiner littéralement dans le CSS.

.page {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: 60px 1fr 40px;
  grid-template-areas:
    "entete entete"
    "menu contenu"
    "pied pied";
  gap: 16px;
  min-height: 100vh;
}

header  { grid-area: entete; }
nav     { grid-area: menu; }
main    { grid-area: contenu; }
footer  { grid-area: pied; }
grid-template-areas "entete entete" "menu contenu" "pied pied"
Le CSS se lit comme la page : chaque chaîne de caractères est une rangée, chaque mot une zone.

Répéter un nom étale la zone sur plusieurs colonnes : "entete entete" couvre les deux. Mesures sur mon banc (conteneur de 600 px sur 400 px) : l’en-tête occupe 600 px sur 60, le menu 200 px sur 268, le contenu 384 px sur 268 et le pied 600 px sur 40. La rangée centrale, déclarée en 1fr, a bien absorbé tout l’espace restant : 400 px moins l’en-tête, le pied et les deux gouttières donnent exactement 268 px. Et pour réorganiser la page sur mobile, il suffit de redessiner grid-template-areas dans une media query, sans toucher au HTML.

Étape 5 : la grille responsive sans media query

Voici la recette la plus spectaculaire de Grid, celle qui fait des galeries de cartes qui s’adaptent toutes seules à l’écran :

.galerie {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px;
}

Traduction : « crée autant de colonnes que possible, chacune d’au moins 150 px, et partage l’espace restant entre elles ». Mesures : dans un conteneur de 600 px, mes six cartes se rangent en 3 colonnes de 189,33 px sur deux rangées. Le même code dans un conteneur de 390 px (une largeur d’écran de téléphone) donne 2 colonnes de 187 px. Zéro media query, zéro JavaScript : le navigateur refait le calcul à chaque redimensionnement.

Un raffinement pour finir : auto-fit a un jumeau, auto-fill, et la différence ne se voit que quand il y a peu d’éléments. Avec seulement 2 cartes dans mon conteneur de 600 px, auto-fit replie les pistes vides et laisse les cartes s’élargir à 292 px chacune. auto-fill, lui, garde une troisième colonne vide : mes 2 cartes restent à 189,33 px, comme si la grille attendait une carte de plus. Les deux comportements se défendent ; il faut juste choisir en connaissance de cause.

Et le centrage ?

Grid a aussi sa recette de centrage parfait, encore plus courte que celle de Flexbox :

.grille {
  display: grid;
  place-items: center;
}

Vérifié au pixel : un bloc de 120 px sur 60 dans un conteneur de 600 px sur 200 atterrit à 240 px du bord gauche et 70 px du haut, un centrage exact sur les deux axes. place-items est le raccourci de align-items et justify-items, les cousines des propriétés d’alignement vues avec les dimensions et le positionnement en CSS.

Ce qu’il faut retenir

Cinq étapes suffisent pour couvrir l’essentiel de Grid en production : grid-template-columns avec l’unité fr pour dessiner les colonnes, gap pour les gouttières, les numéros de ligne pour les placements précis, grid-template-areas pour le gabarit de page et repeat(auto-fit, minmax()) pour les grilles fluides. Gardez aussi les deux chiffres pièges de cet article : une colonne 1fr qui passe de 300 à 268 px quand le gap arrive, et un bloc qui refuse de s’étirer à cause d’une hauteur fixe. Chaque valeur citée ici a été mesurée dans un navigateur avant d’être écrite, et c’est la meilleure habitude à prendre : quand une grille vous surprend, mesurez, la réponse est toujours dans les chiffres.