Vous construisez un dashboard : un header, une sidebar, une zone de contenu principal, un footer. Vous partez sur Flexbox parce que c’est l’outil de mise en page que vous connaissez, et vingt minutes plus tard vous avez trois conteneurs display: flex imbriqués, une sidebar qui ne garde sa largeur qu’avec un nombre magique, et un footer qui flotte à mi-hauteur de la page quand le contenu est court. Rien de tout cela n’est un bug de Flexbox. Vous utilisez un outil à une dimension sur un problème à deux dimensions.

Flexbox répartit l’espace le long d’une seule ligne, en ligne ou en colonne. Grid place les éléments sur des lignes et des colonnes à la fois, et ces lignes et colonnes sont liées entre elles. La plupart des confusions entre Grid et Flexbox viennent d’avoir sauté cette distinction.

Flexbox est unidimensionnel, Grid est bidimensionnel

Les éléments flex vivent sur un seul axe, défini par flex-direction. Avec flex-direction: row, chaque enfant s’aligne de gauche à droite (ou de droite à gauche avec row-reverse) ; avec column, ils s’empilent de haut en bas. Il existe un axe transversal, sur lequel agit align-items. Mais Flexbox ne demande jamais à un élément d’une ligne de s’aligner avec un élément d’une autre ligne. Même quand les éléments passent à la ligne, chaque ligne obtenue est mise en page indépendamment des autres.

1
2
3
4
5
6
7
.toolbar {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

Les éléments Grid s’insèrent dans des lignes et des colonnes que vous définissez à l’avance, et vous pouvez positionner et dimensionner chaque élément selon les deux axes à la fois.

1
2
3
4
5
6
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
}

1fr est une unité de fraction. Le navigateur soustrait d’abord les pistes fixes (240px), puis répartit ce qui reste entre les pistes en fr. Donc grid-template-columns: 1fr 2fr donne à la deuxième colonne le double de la part d’espace restant de la première, pas le double de sa largeur totale.

Comment construire une navbar avec Flexbox

Une navbar est l’exemple type de mise en page à une dimension : un logo, quelques liens et un bouton sur une seule ligne, avec l’espace réparti entre eux.

1
2
3
4
5
6
7
8
9
<nav class="navbar">
  <div class="logo">Acme</div>
  <ul class="links">
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/docs">Docs</a></li>
    <li><a href="/blog">Blog</a></li>
  </ul>
  <button class="cta">Sign up</button>
</nav>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
}

.links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
}

justify-content: space-between fixe le logo contre le bord gauche et le bouton contre le bord droit, avec la liste de liens au milieu. align-items: center garde les trois éléments centrés verticalement même si le logo est plus haut que le bouton. La liste de liens est son propre conteneur flex, donc son espacement vient de gap et vous ne touchez jamais à une margin.

Réduisez le viewport et rien ne passe à la ligne, car flex-wrap vaut nowrap par défaut : les éléments se réduisent jusqu’à leur largeur de contenu minimale puis débordent de la navbar. Ajoutez flex-wrap: wrap et les éléments qui ne rentrent plus passent à une seconde ligne. Cette seconde ligne est mise en page indépendamment et ne s’alignera sur rien de la première. Pour une navbar, ça passe. Pour une mise en page où les lignes doivent s’aligner entre elles, c’est le signe que vous avez atteint la limite de ce que fait Flexbox.

Comment construire un dashboard avec CSS Grid

Les zones de grille nommées permettent de lire une mise en page sans avoir à traduire mentalement des numéros de lignes et de colonnes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.dashboard {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr 48px;
  grid-template-areas:
    "sidebar header"
    "sidebar main"
    "sidebar footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

Chaque nom dans grid-template-areas doit former un rectangle, et les chaînes dessinent littéralement la page : on voit que la sidebar s’étend sur les trois lignes juste en lisant le CSS. La sidebar a une largeur fixe de 240px, le header et le footer ont des hauteurs explicites, et main prend tout ce qui reste puisque sa ligne vaut 1fr. Le footer reste collé en bas du viewport sur une page courte, sans positionnement absolu et sans JavaScript, car min-height: 100vh sur le conteneur combiné à une ligne 1fr suffit.

La version Flexbox de la même page nécessite deux conteneurs : une ligne flex pour la sidebar et le contenu, puis une colonne flex à l’intérieur du contenu pour le header, le main et le footer, avec flex: 1 sur main pour repousser le footer vers le bas. Ça fonctionne. Vous empilez deux mises en page à une dimension pour simuler une mise en page à deux dimensions, et chaque changement de structure de page implique de toucher aux deux.

Une grille de cartes responsive sans media query

Ce seul pattern remplace une bonne partie du code répétitif à base de media queries : une grille de cartes qui ajoute ou retire des colonnes à mesure que le viewport change.

1
2
3
4
5
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
}

repeat(auto-fit, minmax(220px, 1fr)) indique au navigateur de caler autant de colonnes d’au moins 220px que la ligne le permet, puis de les étirer pour remplir l’espace. Un conteneur de 1400px obtient cinq colonnes d’environ 260px ; un téléphone obtient une seule colonne pleine largeur. Aucun breakpoint nulle part.

auto-fit et auto-fill ne se distinguent que lorsque vous avez moins de cartes qu’il n’en faut pour remplir une ligne. auto-fit supprime les pistes vides, donc les cartes réelles s’étirent sur toute la ligne. auto-fill conserve les pistes vides, donc les cartes gardent la largeur qu’elles auraient dans une ligne pleine et le reste de la ligne reste vide. Utilisez auto-fill quand la largeur des cartes doit rester constante quel que soit leur nombre, par exemple une grille de produits où trois résultats ne devraient pas gonfler jusqu’à occuper un tiers de la page chacun. Utilisez auto-fit quand une ligne à moitié vide est pire visuellement que des cartes larges, par exemple un dashboard avec deux ou trois tuiles de synthèse.

Comment combiner Grid et Flexbox

Vous choisissez rarement l’un des deux pour une page entière. Grid gère le squelette ; Flexbox gère le contenu à une dimension à l’intérieur de chaque zone.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

Chaque .card occupe une cellule de la grille et s’étire à la hauteur de cette cellule, puisque les éléments Grid s’étirent par défaut. À l’intérieur, header, body et footer forment une seule colonne, donc justify-content: space-between place le footer en bas de chaque carte, quelle que soit la quantité de texte dans le body. Le même réflexe s’applique à n’importe quel composant : dès qu’un bloc interne doit aligner plusieurs lignes entre elles, c’est encore Grid, imbriqué dans le Flexbox déjà en place.

Si vous utilisez Grid pour basculer entre une vue en cartes et une vue en liste, stockez le choix du visiteur dans localStorage. C’est un seul appel à setItem, et le choix survit aux rechargements et à la fermeture des onglets sans aller-retour serveur.

Quand utiliser subgrid pour des grilles imbriquées

Une grille imbriquée dans un élément de grille démarre son propre jeu de pistes, indépendant de celui du parent. Cela pose problème dès que vous voulez que les lignes internes d’une carte (image, titre, description, prix) s’alignent sur toute une rangée de cartes. En posant grid-template-rows: subgrid (ou grid-template-columns: subgrid) sur la grille imbriquée, celle-ci réutilise les pistes du parent au lieu d’en définir de nouvelles, si bien que la ligne du titre de chaque carte s’aligne avec celle de ses voisines, même si chaque carte reste son propre conteneur de grille. Subgrid fonctionne dans tous les navigateurs majeurs actuels, vous pouvez donc l’utiliser directement plutôt que de forcer des hauteurs égales avec des valeurs fixes.

Quand Flexbox reste le meilleur choix, même si Grid pourrait le faire

Grid peut mettre en page une seule ligne avec grid-auto-flow: column. Mais pour un contenu réellement unidimensionnel, Flexbox a quelque chose que Grid n’a pas : flex-grow et flex-shrink permettent à chaque élément de réclamer plus ou moins d’espace restant selon son propre contenu. C’est exactement ce qu’il faut pour une barre de recherche qui doit occuper tout l’espace qu’un bouton et une icône de largeur fixe lui laissent. Par défaut, choisissez Grid quand lignes et colonnes doivent être liées entre elles ; utilisez Flexbox quand vous mettez en page une seule ligne et que vous voulez un grow et un shrink par élément, pas seulement un alignement.

L’erreur inverse existe aussi. Certains construisent une mise en page multi-colonnes avec flex-wrap: wrap et une width en pourcentage sur chaque élément, pour simuler une grille. Ça tient jusqu’à ce que la dernière ligne contienne moins d’éléments que les autres. Là, space-between envoie les deux derniers éléments aux extrémités opposées, ou flex-grow les étire plus large que tout ce qui précède. Le navigateur continue de distribuer des éléments le long de lignes, pas de remplir une grille. Si vous comptez des colonnes, utilisez Grid.

Si vous avez encore besoin de JavaScript au resize, par exemple pour repositionner un tooltip à côté d’une carte, appliquez un debounce au handler plutôt que de l’exécuter à chaque événement resize déclenché par le navigateur.

Grid ou Flexbox : comment choisir

Une seule question tranche : les éléments ont-ils des lignes et des colonnes qui doivent s’aligner entre elles, ou s’agit-il d’une seule ligne d’éléments ?

SituationUtiliser
Navbar, groupe de boutons, toolbarFlexbox
Squelette de page : header, sidebar, main, footerGrid
Grille de cartes ou de produits responsiveGrid (auto-fit/auto-fill + minmax)
Centrer un élément, ou répartir uniformément quelques élémentsFlexbox
Formulaire avec labels et inputs alignésGrid
Contenu à l’intérieur d’une seule cellule de grille (le header/body/footer d’une carte)Flexbox, imbriqué dans la grille

La prochaine fois que vous ouvrez une feuille de style pour une nouvelle mise en page, esquissez d’abord les boîtes sur papier. Si vous tracez des lignes qui se croisent, c’est Grid. Si chaque boîte tient sur une seule ligne, c’est Flexbox.

Articles associés