Utiliser le display « grid » de CSS

Comme chacun sait, le mode d'affichage d'un élément HTML se contrôle via la propriété CSS display.
Utiliser le display grid en CSS
Depuis fort longtemps déjà, ce mode peut être une des deux variantes définie dans la spécification du module Grid de CSS : grid ou inline-grid. Cela a permis d'éradiquer les <table>, <tr> et autres <td>.
A l'occasion du retour sur le développement d'une application Web laissée en chantier depuis bien trop longtemps, l'auteur de ces lignes a mis en ordre des notes sur l'utilisation de ce mode d'affichage. Dès lors, pourquoi ne pas en faire profiter tout le monde ?

Avertissements

Il ne s'agit pas d'explorer dans les moindres détails ce mode d'affichage, qui peut être très subtil, mais de permettre de s'en saisir rapidement pour réaliser des mises en forme qui peuvent tout de même être élaborées.
Aussi, cliquez ici si vous êtes du genre à préférer partir d'exemples plutôt que d'explications.

Quelques définitions

Les grilles CSS sont apparues dans Firefox il y a bien longtemps, toute fin 2016. Dans ce navigateur, les outils de débogage comprennent un Grid Inspector qui permet de visualiser les cellules sous-jacentes d'une grille par rapport auxquelles les éléments visibles sont disposés :
Le Grid Inspector dans le debogueur de Firefox
La spécification de Grid est lisible, mais les guides de MDN le sont plus encore - en fait, ils sont excellents. Noter l'existence d'une version 2 de Grid, qui n'était pas encore implémentée par les navigateurs à la date où ces notes ont été rédigées, et qui de toute manière ne l'a été que tout récemment. Ainsi la fameuse valeur subgrid à quoi se résume la nouveauté de la version 2 - mais c'est une valeur très conséquente - n'est mentionnée ici que depuis 2023 pour ce qui concerne Firefox.
Avant toute chose, un peu de vocabulaire :
Line Une ligne qui divise la grille horizontalement ou verticalement
Track Un espace entre des lignes (ie : une rangée ou une colonne)
Gutter La gouttière, espace entre deux rangées adjacentes, ou entre deux colonnes adjacentes
Area Une zone qui occupe intégralement ou partiellement une ou plusieurs cellules
Item Un élément placé dans une zone
Pour éviter toute confusion en français par la suite, une ligne au sens d'une rangée de cellules sera désignée ici comme une rangée.
Noter que la spécification de CSS n'utilise jamais le terme de cellule. Il n'y est jamais question que de rangées et de colonnes dont les intersections définissent des zones :
The grid is an intersecting set of horizontal and vertical grid lines that divides the grid container’s space into grid areas, into which grid items (representing the grid container’s content) can be placed.
Toutefois, comme on le verra, une zone peut s'étaler sur plusieurs rangées et/ou plusieurs colonnes. Dans ces conditions, il est pratique de pouvoir distinguer deux découpages qu'on désignera ici comme le découpage géométrique en cellules et le découpage fonctionnel en zones, une zone correspondant par défaut à une cellule. Bref, un élément est positionné dans une zone, qui recouvre une ou plusieurs cellules.
Noter que les indices des rangées et des colonnes sont exprimés à partir de 1, et non de 0. Attention donc dans la lecture des coordonnées : la cellule (1, 1) est la première en haut à gauche de la grille.

Grid, kezako ?

Maintenant qu'il est possible de s'entendre sur les termes, qu'est-ce que Grid en CSS ? C'est un modèle de disposition sur deux axes, pour dépasser les limites de modèles précédents, dont Flexible Box. L'intérêt d'une grille résiderait dans la flexibilité des dispositions qu'il serait possible de concevoir, pour les adapter à des affichages que le Web mobile rend toujours plus variés. Pour le développeur qui n'a jamais utilisé que des tableaux, recourir à ce modèle permet de faire une grande économie de balises et de recycler facilement agencements puisqu'il peuvent être décrits en CSS et non en HTML, et à ce titre factorisés dans une section <style> ou une feuille de styles et rappelés n'importe où dans le code HTML par le truchement de références à des styles CSS (classe, identifiant, etc.)
Avec Grid, il s'agit de décrire une disposition en spécifiant comment les rangées et les colonnes sont dimensionnées - ce qui décrit par intersection comment chaque cellule est dimensionnée -, puis d'associer un élément HTML à la disposition - le conteneur -, et ses éléments HTML enfants aux zones, chaque zone correspondant par défaut à une cellule si bien qu'elle n'a pas à être décrite explicitement.
<style>
.grid0 {
	display: grid;
	grid-template-columns: 100px 200px;
	grid-template-rows: 50px 75px;
}
</style>
<div class="grid0">
	<div style="grid-row:1;grid-column:1">(1, 1)</div>
	<div style="grid-row:2;grid-column:1">(2, 1)</div>
	<div style="grid-row:1;grid-column:2">(1, 2)</div>
	<div style="grid-row:2;grid-column:2">(2, 2)</div>
</div>
Pour simplifier le référencement des rangées et des colonnes, il est possible d'utiliser des noms plutôt que des indices :
	
<style>
.grid {
	display: grid;
	grid-template-columns: [the-top] 100px [the-bottom] 200px;
	grid-template-rows: [the-left] 50px [the-right] 75px;
}
</style>

<div class="grid">
	<div style="grid-row:the-top;grid-column:the-left">(1, 1)</div>
	...
</div>
Pour simplifier la définition de la disposition, il est possible de spécifier quand un motif doit être répété - le nommage peut toujours être utilisé, mais il toutes les rangées / colonnes répétées seront homonymes :
<style>
.grid {
	display: grid;
	grid-template-columns: repeat(2, [some-column] 10px);
	grid-template-rows: [the-top] 75px repeat(3, 25px);
}
</style>
Attention ! Ne pas faire figurer d'espace entre repeat et la parenthèse ouvrante.
Il est possible de demander la répétition de plusieurs rangées ou colonnes, comme ici où trois colonnes sont répétées quatre fois :
repeat(4, [the-left] 10px [the-center] 100px 50px)
Lorsque des rangées ou des colonnes sont homonymes, notamment parce qu'elles ont été ainsi répétées, une référence au nom correspond à la première occurrence. Pour référencer les autres, il faut utiliser un indice qui démarre à 1. Par exemple, dans le cas précédent :
<div style="grid-row:the-top;grid-column:some-column 2">(1, 3)</div>
Enfin, il est parfaitement possible de se contenter de définir la disposition et de laisser le navigateur disposer les éléments, ce qu'il fera dans l'ordre dans lequel ils lui sont fournis, d'abord en colonnes, puis en rangées. Par exemple :
<style>
.grid {
	display: grid;
	grid-template-columns: 100px 200px;
	grid-template-rows: 50px 75px;
}
</style>
<div class="grid">
	<div>(1, 1)</div>
	<div>(1, 2)</div>
	<div>(2, 1)</div>
	<div>(2, 2)</div>
</div>

Cellules et zones

Comme déjà mentionné, une zone correspond par défaut à une cellule, si bien qu'elle n'a pas à être décrite explicitement - encore une fois, la spécification de CSS n'utilise jamais le terme de cellule, et cette distinction entre découpage géométrique en cellules et découpage fonctionnel en zones est ad hoc. Toutefois, il est possible de définir explicitement une zone pour indiquer quelle(s) cellule(s) elle comprend, ce qui implique de la nommer :
+-------+---+
|       |   |
+---+---+   |
|   |   |   |
|   +---+---+
|   |       |
+-----------+
<style>
.grid {
	display: grid;
	grid-template-areas: "areaTop areaTop areaRight"
						 "areaLeft areaCenter areaRight"
						 "areaLeft areaBottom areaBottom";
	grid-template-columns: 100px min-content 200px;
	grid-template-rows: 50% 25% 25%;
}
</style>
<div class="grid">
	<div style="grid-area:areaLeft">Left</div>
	<div style="grid-area:areaRight">Right</div>
	<div style="grid-area:areaTop">Top</div>
	<div style="grid-area:areaBottom">Bottom</div>
	<div style="grid-area:areaCenter">Center</div>
</div>
Pour ne pas attribuer une cellule à une zone, il suffit de la désigner par . dans grid-template-areas.
Le placement d'un élément dans une zone peut être demandé via l'une des propriétés de placement :
grid-row-start
grid-row-end
grid-column-start
grid-column-end
Les propriétés suivantes sont des raccourcis des précédentes :
grid-row
grid-column
grid-area
Pour reprendre l'exemple précédent, la version non raccourcie de la disposition serait... :
<div class="grid">
	<div style="grid-row-start:2;grid-row-end:-1;grid-column-start:1;grid-column-end:1">Left</div>
	<div style="grid-row-start:1;grid-row-end:-2;grid-column-start:3;grid-column-end:3">Right</div>
	<div style="grid-row-start:1;grid-row-end:1;grid-column-start:1;grid-column-end:-2">Top</div>
	<div style="grid-row-start:3;grid-row-end:3;grid-column-start:2;grid-column-end:-1">Bottom</div>
	<div style="grid-row-start:2;grid-row-end:2;grid-column-start:2;grid-column-end:2">Center</div>
</div>
...et une version un peu raccourcie serait :
<div class="grid">
	<div style="grid-row:2/-1;grid-column:1">Left</div>
	<div style="grid-row:1/-2;grid-column:3">Right</div>
	<div style="grid-row:1;grid-column:1/-2">Top</div>
	<div style="grid-row:3;grid-column:2/-1">Bottom</div>
	<div style="grid-row:2;grid-column:2">Center</div>
</div>
Noter que la fusion de rangées et de colonnes doit être spécifiquement demandée à l'aide d'indices négatifs dans un repère des cellules centré sur l'angle inférieur droit de la grille. Ainsi, pour désigner la cellule (i, j) dans une grille de L x H, il faut utiliser les indices (i - L - 1, j - H - 1). Bref, la cellule tout en bas à droite est (-1, -1), et celle tout en haut à gauche est (-L, -H).
Il est donc parfaitement possible de spécifier :
<div style="grid-row-start:2;grid-row-end:3;grid-column-start:1;grid-column-end:1">Left</div>
Mais si la disposition générale sera celle attendue (la cellule sous celle contenant "Left" ne sera pas occupée), il n'y aura pas de fusion (la cellule contenant "Left" et celle inoccupée se trouvant dessous ne seront pas fusionnées).
Alternativement, la fusion peut être demandée à l'aide du mot-clé span. L'intérêt (ou l'inconvénient) sur le recours à des indices négatifs, c'est que plutôt que de spécifier la rangée ou la colonne où se termine la fusion, on spécifie le nombre des rangées ou de colonnes de cette dernière. Ainsi, dans l'exemple précédent, les notations suivantes sont équivalentes :
<div style="grid-row:2/-1;grid-column:1">Left</div>
<div style="grid-row:2 / span 2;grid-column:1">Left</div>
Noter que cela est tout aussi possible si les rangées et les colonnes sont nommées. Par exemple :
<style>
.grid {
	display:grid;
	grid-template-columns: [the-left] 100px [the-right] 150px;
	grid-template-rows: repeat(2, [some-line] 25px);
}
</style>
<div class="grid">
	<div class="cell" style="grid-row:some-line / span some-line 2;grid-column:the-left">Left</div>
	<div class="cell" style="grid-row:some-line;grid-column:the-right">Right Top</div>
	<div class="cell" style="grid-row:some-line 2;grid-column:the-right">Right Bottom</div>
</div>
Toutefois, il semble impossible d'utiliser alors des indices négatifs, comme dans grid-row:some-line / span some-line -1...
Comme grid-area est une notation raccourcie pour désigner tant les rangées que les colonnes, sa syntaxe est plus complexe. Soit elle fait référence par son nom à une zone définie dans grid-template-areas, soit elle décrit la zone. Pour reprendre toujours le même exemple, une version basée sur grid-area serait la suivante :
<div class="grid">
	<div style="grid-area:2/1/-1/1">Left</div>
	<div style="grid-area:1/3/-2/3">Right</div>
	<div style="grid-area:1/1/1/-2">Top</div>
	<div style="grid-area:3/2/3/-1">Bottom</div>
	<div style="grid-area:2/2/2/2">Center</div>
</div>
Attention! La syntaxe est grid-row-start / grid-column-start / grid-row-end / grid-column-end : les -start avant les -end.

Alignement

Un élément occupe une certaine position dans la zone où il est placé. On parle d'alignement, mais mieux vaudrait parler plus généralement de distribution, car parmi les alignements possibles, celui par défaut est stretch, qui non seulement affecte la position mais aussi les dimensions de l'élément puisque ce dernier est alors positionné en haut à gauche de la zone, et étiré jusqu'à occuper toute la surface de cette dernière. En fait, l'alignement consiste à contrôler l'espace vide entre les bords d'un élément et ceux de sa zone en jouant sur la position et les dimensions de l'élément.
L'alignement peut être ajusté horizontalement (row axis) et verticalement (column axis), pour tous les éléments ou par élément.
  • pour modifier l'alignement de tous les éléments, utiliser justify-items (alignement horizontal) et align-items (alignement vertical) dans les styles du conteneur ;
  • pour modifier l'alignement d'un élément, utiliser justify-self (alignement horizontal) et align-self (alignement vertical) dans les styles de l'élément.
Les valeurs possibles sont :
auto
normal
start Aligner à gauche (en haut)
end Aligner à droite (en bas)
center Aligner au milieu
stretch Etendre sur toute la surface (par défaut)
baseline
first baseline
last baseline
Tout comme il est possible de gérer l'espace vide entre les bords des éléments et ceux de leurs zones, il est possible gérer l'espace vide entre les bords des cellules et ceux du conteneur. Les propriétés sont justify-content (alignement horizontal) et align-content (alignement vertical). Par exemple :
<style>
.grid {
	display:grid;
	grid-template-rows:100px;
	grid-template-columns:100px;
	width:200px;
	height:200px;
	justify-content:center;
	align-content:center;
	background-color:yellow;
}
</style>
<div class="grid">
	<div style="border:1px solid black">Cell</div>
</div>
Les valeurs possibles sont :
normal
start Aligner à gauche (en haut)
end Aligner à droite (en bas)
center Aligner au milieu
stretch Etendre sur toute la surface
space-around
Répartir l'espace libre sous la forme d'une bordure autour de chaque cellule :
  • l'espace entre deux zones est toujours le même ;
  • l'espace avant la première cellule et celui après la dernière cellule vaut la moitié de l'espace entre deux cellules.
+------------------------+
|  +--+    +--+    +--+  |
|  |  |    |  |    |  |  |
|  +--+    +--+    +--+  |
+------------------------+
space-beween
Répartir l'espace libre entre les cellules uniquement :
+------------------------+
+--+       +--+       +--+  
|  |       |  |       |  |
+--+       +--+       +--+  
+------------------------+
space-evenly
Comme space-around, mais tous les espaces sont égaux :
+------------------------+
|   +--+   +--+   +--+   |
|   |  |   |  |   |  |   |
|   +--+   +--+   +--+   |
+------------------------+
baseline
first baseline
last baseline
Noter qu'il est bien question d'alignement de cellules et non de zones. Quand une zone s'étend sur plusieurs cellules, elle peut être déformée par ce processus. Par exemple... :
<style>
.grid {
	display:grid;
	grid-template-columns:50px 50px 50px;
	width:200px;
	grid-template-areas: "areaLeft areaLeft areaRight";
	justify-content:space-evenly;
}
</style>
<div class="grid">
	<div style="grid-area:areaLeft">1</div>
	<div style="grid-area:areaRight">2</div>
</div>
...d'où ce découpage en cellules... :
+------------------------+
|   +--+   +--+   +--+   |
|   |  |   |  |   |  |   |
|   +--+   +--+   +--+   |
+------------------------+
...qui produit ce découpage en zones :
+------------------------+
|  +----------+   +--+   |
|  |1         |   |2 |   |
|  +----------+   +--+   |
+------------------------+
Pour ajuster la gouttière entre les cellules (et non les zones, car l'espacement des cellules entraîne certes celui des zones, mais aussi le redimensionnement des zones quand elles s'étalent sur plusieurs cellules), utiliser les propriétés row-gap et column-gap, ou gap qui en est un raccourci.

Raccourcis

Dans tous les exemples qui viennent d'être donnés, le conteneur est un <div> qui s'étale sur toute la largeur de la page. C'est parce que la valeur grid de la propriété display le dispose comme un bloc (sur le modèle de display:block). La valeur inline-grid permet de le disposer comme un bloc en ligne (sur le modèle de display:inline-block), et donc de limiter sa largeur à celle de son contenu. Attention, car la spécification précise :
Grid containers are not block containers, and so some properties that were designed with the assumption of block layout don’t apply in the context of grid layout.
La liste des propriétés relatives aux grilles comprend de multiples raccourcis pour alléger la définition d'une grille. En particulier, la propriété grid-template est un raccourci des suivantes... :
grid-template-rows
grid-template-columns
grid-template-areas
...et la propriété grid est un raccourci encore plus général, puisqu'il l'est pour toutes les propriétés suivantes :
grid-template-rows
grid-template-columns
grid-template-areas
grid-auto-rows
grid-auto-columns
grid-auto-flow
Noter l'absence des propriétés relatives aux gouttières, les gouttières étant ramenées à 0.
Ainsi, les notations suivantes sont équivalentes :
.grid {
	display: grid;
	grid-template-columns: 100px 150px 200px;
	grid-template-rows: 25px 50px 75px;
	grid-template-areas: "areaTop areaTop areaRight"
						 "areaLeft areaCenter areaRight"
						 "areaLeft areaBottom areaBottom";
}

.grid {
	display: grid;
	grid-template: "areaTop areaTop areaRight" 25px
				   "areaLeft areaCenter areaRight" 50px
				   "areaLeft areaBottom areaBottom" 75px
				   / 100px 150px 200px;
}

.grid {
	display: grid;
	grid: "areaTop areaTop areaRight" 25px
		  "areaLeft areaCenter areaRight" 50px
		  "areaLeft areaBottom areaBottom" 75px
		  / 100px 150px 200px;
}

Grille implicite

Une grille définie à l'aide des propriétés grid-template-rows, grid-template-columns, et grid-template-areas est dite explicite : les rangées et le colonnes sont connues à l'avance. Toutefois, lorsqu'un élément est rajouté en dehors de ces rangées et colonnes, de nouvelles rangées et colonnes sont créées automatiquement. On parle alors d'une grille implicite.
Dans une telle grille, les rangées et les colonnes sont formatées comme spécifié via les propriétés suivantes :
grid-auto-rows
grid-auto-columns
grid-auto-flow
Par exemple... :
<div style="display:grid;grid-auto-columns:100px;grid-auto-rows:50px">
	<div style="grid-row:1 / span 2;grid-column:1">A</div>
	<div style="grid-row:1;grid-column:2">B</div>
	<div style="grid-row:2;grid-column:2">C</div>
</div>
...définit implicitement la grille suivante :
+---+---+
|   | B |
| A +---+
|   | C |
+---+---+

Contrôle du placement

Et grid-auto-flow ? Cette propriété permet de contrôler la disposition progressive des éléments. Les valeurs possibles sont :
row Les éléments sont disposés en remplissant une rangée, puis une autre, etc. (par défaut)
column Les éléments sont disposés en remplissant une colonne, puis une autre, etc.
dense Un backtracking permet de placer les petits éléments là où peuvent au mieux combler les trous (l'ordre des éléments n'est donc plus assuré)
La dernière valeur peut être combinée aux deux premières.
Par exemple... :
<div style="display:grid;grid-auto-flow:column;grid-template-rows:repeat(2,auto)">
	<div>A</div>
	<div>B</div>
	<div>C</div>
	<div>D</div>
</div>
...permet de disposer les éléments colonne après colonne, sur deux lignes :
+---+---+
| A | C |
|---+---+
| B | D |
+---+---+

Grille dynamique

Tout cela est bien beau, mais se contenter d'expliquer la manière de décrire une disposition, ce serait manquer l'autre gros avantage du modèle de disposition Grid sur les tableaux, à savoir l'ajustement en fonction des spécificités du média.
En fait, cela n'est qu'une conséquence de la possibilité de factoriser la description de la disposition au niveau des styles. Il s'agit d'exploiter la at-rule @media, pour réaliser une requête média. En fonction des caractéristiques du média, il est possible de définir d'une manière ou d'une autre les styles auxquelles les éléments font référence pour être agencés.
Par exemple, pour disposer trois éléments A, B et C ainsi si la largeur de l'écran est supérieure à 400px... :
+---+---+
| A | B |
+---+---+
|   C   |
+---+---+
...et ainsi si elle est inférieure à 400px :
+---+
| A |
+---+
| B |
+---+
| C |
+---+
<style>
@media (width <= 400px) {
	.grid {
		display: grid;
		grid-template-areas: "A"
							 "B"
							 "C";
	}
}
@media (width > 400px) {
	.grid {
		display: grid;
		grid-template-areas: "A B"
							 "C C";
	}
}
</style>
<div class="grid">
	<div style="grid-area:A">A</div>
	<div style="grid-area:B">B</div>
	<div style="grid-area:C">C</div>
</div>
Utiliser le display « grid » de CSS