Créer son layout en CSS sans prise de tête !
Les années passant j'ai eu l'occasion de travailler avec pas mal de framework JavaScript, CSS et même des bibliothèques de composant. Un truc qui me parait toujours absurde c'est à quel point on travaille avec beaucoup trop de <div> pour tout, qu'on fait des structures HTML non sens alors qu'on peut faire tellement plus simple. Et vous savez le mieux ? Faire simple c'est plus puissant au final ! Je vous montre !
Posons le HTML🔗
Comme je l'ai fait dans mes articles CSS récent, je vous donne le HTML, et ensuite : on y touche plus !
<!DOCTYPE html>
<html>
<head>
<title>CSS is Magic</title>
<link rel="stylesheet" href="../reset.css" />
<link rel="stylesheet" href="./main.css" />
<link rel="stylesheet" href="./layouts.css" />
</head>
<body>
<header>
<a href="../index.html" title="Back to home">🏠</a>
<form id="layout">
<span style="margin-right: 1rem">Choose a layout:</span>
<label
><input type="radio" name="layout" value="none" checked /> None</label
>
<label><input type="radio" name="layout" value="flex" /> Flex</label>
<label
><input type="radio" name="layout" value="flex-wrap" /> Flex
Wrap</label
>
<label><input type="radio" name="layout" value="grid" /> Grid</label>
<label><input type="radio" name="layout" value="bento" /> Bento</label>
<label
><input type="radio" name="layout" value="dispatched" />
Dispatched</label
>
<label
><input type="radio" name="layout" value="stairs" /> Stairs
Grid</label
>
</form>
</header>
<main id="container">
<article>
<img src="../img/persona/emma.jpg" />
<main>
<p>Dubois Emma</p>
<em>Chef de projet digital</em>
</main>
</article>
<article>
<img src="../img/persona/lucas.jpg" />
<main>
<p>Moreau Lucas</p>
<em>Développeur full-stack</em>
</main>
</article>
<article>
<img src="../img/persona/camille.jpg" />
<main>
<p>Lefèvre Camille</p>
<em>Responsable marketing</em>
</main>
</article>
<article>
<img src="../img/persona/thomas.jpg" />
<main>
<p>Bernard Thomas</p>
<em>Data scientist</em>
</main>
</article>
<article>
<img src="../img/persona/lea.jpg" />
<main>
<p>Petit Léa</p>
<em>Designer UX/UI</em>
</main>
</article>
<article>
<img src="../img/persona/antoine.jpg" />
<main>
<p>Girard Antoine</p>
<em>Responsable logistique</em>
</main>
</article>
</main>
</body>
</html>Html
Le code HTML contient une structure basique, le menu en haut dans une balise <header>, puis un ensemble de carte pour afficher des personnes sous la forme d'une série d'<article> dans une balise <main>.
Je vous donne aussi les fichiers de styles de base.
Le reset.css qui va nous donner une base de style plus propre :
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: unset;
}
button,
input,
textarea,
select {
font: inherit;
}
img,
picture,
svg,
canvas {
display: block;
max-inline-size: 100%;
block-size: auto;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}CSS
Et le main.css qui va essentiellement contenir le style du menu et des cartes des personnes :
/* HEADER */
body {
& > header {
display: flex;
justify-content: stretch;
align-items: center;
padding: 1rem;
background-color: black;
color: white;
a {
font-size: 2rem;
line-height: 2rem;
}
form {
flex: 1;
display: flex;
justify-content: center;
align-items: baseline;
label {
padding: 0.5rem 1rem;
/* transition: background-color 0.5s; */
border-radius: 1rem;
cursor: pointer;
input[type="radio"] {
width: 1;
height: 1;
margin: 0;
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
&:has(input:checked) {
background-color: lightgrey;
color: black;
}
}
}
}
}
/* CARD */
article {
display: flex;
flex-direction: column;
justify-content: space-around;
align-content: space-around;
background-color: antiquewhite;
border-radius: 1rem;
padding: 0.5rem;
height: 100%;
width: 100%;
img {
object-fit: cover;
aspect-ratio: 1/1;
max-width: 100%;
max-height: calc(100% - 3em);
border-radius: 50%;
border: 3px solid chocolate;
margin: auto;
}
main {
text-align: center;
p,
em {
line-height: 1em;
margin-top: 0;
margin-bottom: 0;
}
}
}
/* LAYOUTS */
#container {
width: fit-content;
margin: auto;
padding: 1rem;
}CSS
Pour l'instant dans le fichier layouts.css, on va juste mettre ça :
#container {
--card-size: 18rem;
}
body:has(header form input[name="layout"][value="none"]:checked) {
#container {
article {
max-width: var(--card-size);
}
}
}CSS
Ce morceau de code va permettre d'avoir accès à la taille de la carte partout pour la suite. La taille de 18rem (pour rappel 1rem = 1 fois la largeur du caractère m dans la taille de police utilisé par défaut dans la page) est purement arbitraire.
J'en profite pour définir le premier layout "none" qui va uniquement forcer une taille raisonnable de chaque carte en se basant sur un menu fonctionnant de la même façon que décrit dans l'article Un menu 100% en CSS.
Normalement, vous devriez avoir ça :
À partir de maintenant, on ne va toucher qu'au fichier layouts.css pour créer 6 layouts différents.
Layout 1 : Flex🔗
C'est basique, mais on a tendance à oublier les basiques donc un petit rappel ne fait pas de mal.
Ajoutons à layouts.css ce code :
...
body:has(header form input[name="layout"][value="flex"]:checked) {
#container {
display: flex;
gap: 0.5rem;
}
}CSS
On dit simplement que notre #container est en display: flex, ce qui implicitement indique flex-direction: row (donc en ligne), puis on ajoute aussi un gap: 0.5rem pour avoir un peu d'espace entre nos cartes.
Avec deux lignes de CSS, on passe toutes nos cartes en ligne, et qui tiennent dans la largeur de la page. Évidemment ce n'est pas parfait, typiquement, sur mobile ça ne va pas bien se comporter et le contenu va être tout tassé et finira même par déborder de l'écran. Mais si on a peu d'élément ça peut largement suffire !
Layout 2 : Flex wrap🔗
body:has(header form input[name="layout"][value="flex-wrap"]:checked) {
#container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
article {
max-width: var(--card-size);
}
}
}CSS
Toujours très simple, on ajoute juste flex-wrap: wrap pour faire en sorte de faire un retour à la ligne quand il n'y a plus de place plutôt que réduire la largeur, et on indique qu'on veut centrer le contenu avec justify-content: center.
On ne le voit pas dans l'exemple mais ces 4 lignes de CSS permettent d'auto-ajuster la répartition des éléments en fonction de la largeur disponible et de chaque élément.
Je vous montre un autre exemple :
En ne gardant que le style du layout j'ai ça :
.key-figures {
display: flex;
flex-wrap: wrap;
gap: 2rem;
.figure-card {
flex: 1 1 auto;
}
}CSS
flex: 1 1 autoétant la version raccourcie deflex-grow: 1(= grandi en fonction de la place disponible) +flex-shrink: 1(= réduit en fonction de la place disponible) +flex-grow: auto(= auto détermine la taille de base).
Ici j'ai des cartes dont la largeur est automatiquement calculée pour prendre l'espace disponible, automatiquement réparti sur deux lignes, et sans dire comment. Les flex layout sont disponibles depuis assez longtemps maintenant pourtant c'est encore trop peu/mal utilisé à mon sens.
Avant de passer à un autre type de layout, je vous dirais une règle simple : les flex layouts sont à utiliser partout où on veut que le layout s'adapte au contenu.
Layout 3 : Grid🔗
Maintenant on va partir sur une série de quatre layouts basé sur display: grid, qui à l'inverse de flex est orienté layout, donc on va poser le layout et le contenu va s'adapter au layout.
body:has(header form input[name="layout"][value="grid"]:checked) {
#container {
display: grid;
grid-template-columns: 1fr 1fr;
justify-items: center;
gap: 0.5rem;
@media screen and (width > 90rem) {
grid-template-columns: 1fr 1fr 1fr;
}
@media screen and (width > 110rem) {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
@media screen and (width > 130rem) {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
article {
width: var(--card-size);
}
}
}CSS
Ici j'ai mis en place une grille très simple, avec une répartition sur 2 à 5 colonnes en fonction de la largeur de l'écran.
En ne définissant que la propriété grid-template-columns on va indiquer le pattern de chaque ligne (je sais que c'est indiqué -columns mais c'est logique, car on définit la taille de chaque colonne sur une ligne), en ne définissant que ça on va avoir automatiquement un nombre de ligne qui s'adapte au contenu. J'indique aussi justify-items: center pour que l'espace en trop soit réparti à gauche et à droite de la grille équitablement.
Ici on est sur une grille très simple mais c'est super efficace en fonction de ce qu'on souhaite faire !
Layout 4 : Bento🔗
Passons à un layout intéressant : le bento. On en voit régulièrement, un peu partout pour afficher plein de cartes mais en mettant en avant plus ou moins certaines, parfois aussi avec des animations. Ici nous allons faire un bento simple, sans animation.
body:has(header form input[name="layout"][value="bento"]:checked) {
#container {
display: grid;
grid-template-columns: repeat(4, var(--card-size));
grid-template-rows: repeat(3, var(--card-size));
align-items: center;
justify-items: center;
gap: 0.5rem;
& > article {
max-width: unset;
max-height: 100%;
}
& > :nth-child(1) {
grid-column: auto / span 2;
grid-row: auto / span 2;
}
& > :nth-child(2) {
grid-column: auto / span 1;
grid-row: auto / span 1;
}
& > :nth-child(3) {
grid-column: auto / span 1;
grid-row: auto / span 2;
aspect-ratio: 1/2;
}
& > :nth-child(4) {
grid-column: auto / span 1;
grid-row: auto / span 2;
aspect-ratio: 1/2;
}
& > :nth-child(5) {
grid-column: auto / span 2;
grid-row: auto / span 1;
aspect-ratio: 2/1;
}
& > :nth-child(6) {
grid-column: auto / span 1;
grid-row: auto / span 1;
}
}
}CSS
L'idée ici c'est de commencer par construire une grille plus grande que ce dont on aurait besoin pour nos cartes (on a 6 cartes, j'ai créé une grille de 4 par 3 donc 12 cases). Ensuite on va placer chaque carte une par une sur notre grille en définissant sa colonne et ligne de départ et le nombre de case à occuper dans chaque direction.
Si on regarde un des positionnements, par exemple le premier :
& > :nth-child(1) {
grid-column: auto / span 2;
grid-row: auto / span 2;
}CSS
On voit grid-column: auto / span 2;, ici auto signifiant "place à la prochaine case disponible" et span 2 signifie "doit s'étaler sur 2 colonnes". Dans la même logique grid-row: auto / span 2 indique de placer dès que possible en s'étalant sur 2 lignes.
On aurait pu choisir explicitement l'emplacement de chaque carte mais c'est beaucoup plus simple de laisser le navigateur choisir, par contre ça veut aussi dire que c'est le navigateur qui choisit l'emplacement. Même si normalement on observera toujours le même placement avec un remplissage "ligne à ligne".
J'ai besoin de jouer avec l'aspect-ratio de chaque élément parce que dans le style de la carte j'ai forcé un aspect-ratio, donc si vous n'en avez pas à la base, vous pouvez surement vous en passer aussi (en fonction du rendu que vous voulez avoir), mais personnellement je voulais m'assurer d'avoir une grille parfaitement régulière.
Layout 5 : Dispatched🔗
Comme les grilles c'est magique (non c'est juste bien pensé 😛), on peut aussi laisser des cases vides dans la grille et positionner dans un ordre arbitraire les différentes cartes.
body:has(header form input[name="layout"][value="dispatched"]:checked) {
#container {
display: grid;
grid-template:
". one three" var(--card-size)
"six four ." var(--card-size)
"five four two" var(--card-size)
/ var(--card-size) var(--card-size) var(--card-size);
align-items: center;
justify-items: center;
gap: 0.5rem;
& > :nth-child(1) {
grid-area: one;
}
& > :nth-child(2) {
grid-area: two;
}
& > :nth-child(3) {
grid-area: three;
}
& > :nth-child(4) {
grid-area: four;
}
& > :nth-child(5) {
grid-area: five;
}
& > :nth-child(6) {
grid-area: six;
}
}
}CSS
C'est finalement assez simple à comprendre pourtant c'est hyper puissant ! Avec la propriété grid-template on va "dessiner" notre grille en nommant les différentes zones qu'on veut placer et en définissant la taille des éléments. Le fonctionnement est je trouve très visuel : on va définir une chaîne de caractère pour chaque ligne, sur chaque ligne on nomme les différentes zones qui sont visibles (un . signifiant "vide"), après le contenu de la ligne on indique la hauteur de la ligne, et on enchaîne comme ça toutes les lignes jusqu'à un / suivi de la taille des colonnes.
Si je prends l'exemple que j'ai écrit ". one three" var(--card-size) va indiquer que la première ligne contient une case vide puis la zone one, puis la zone three et elle doit avoir une hauteur d'une carte ; ensuite la seconde ligne "six four ." var(--card-size) la zone six puis four puis vide ; ensuite la troisième ligne "five four two" var(--card-size) indique qu'on veut la zone five puis four puis two. Vous noterez bien que la zone four est à cheval entre la ligne 2 et 3 ce qui permet qu'elle soit plus grande.
Puis pour chaque carte on va définir sur quelle zone on souhaite l'afficher avec grid-area (attention à ne pas mettre de " sur cette propriété, ça ne fonctionnera).
Les noms des zones sont arbitraires et vous pouvez donc être super explicite avec ce nommage. Par exemple :
body {
grid-template:
"header header" / 5rem
"main aside" / 1fr
"main ." / 1fr
"footer footer" / 10rem
/ 2fr 1fr ;
}CSS
fonctionnera aussi bien que
body {
grid-template:
"h h" / 5rem
"m a" / 1fr
"m ." / 1fr
"f f" / 10rem
/ 2fr 1fr ;
}CSS
Mais la première version est quand même beaucoup plus simple à relire et donc à favoriser !
Layout 6 : Stairs grid🔗
Pour tout vous dire, c'est en construisant un layout comme ça que j'ai eu l'idée d'écrire cet article ! 🤓
L'idée ici c'est de placer tous les éléments en "escalier" : le premier élément, puis le suivant à droite juste à moitié plus bas, puis en continuant de remplir comme ça.
body:has(header form input[name="layout"][value="stairs"]:checked) {
#container {
display: grid;
grid-template:
"one ." 1fr
"one two" 1fr
"three two" 1fr
"three four" 1fr
"five four" 1fr
"five six" 1fr
". six" 1fr
/ var(--card-size) var(--card-size);
@media screen and (width > 90rem) {
grid-template:
"one . ." 1fr
"one two ." 1fr
"three two four" 1fr
"three five four" 1fr
". five six" 1fr
". . six" 1fr
/ var(--card-size) var(--card-size) var(--card-size);
}
justify-items: center;
gap: 0.5rem;
& > :nth-child(1) {
grid-area: one;
}
& > :nth-child(2) {
grid-area: two;
}
& > :nth-child(3) {
grid-area: three;
}
& > :nth-child(4) {
grid-area: four;
}
& > :nth-child(5) {
grid-area: five;
}
& > :nth-child(6) {
grid-area: six;
}
}
}CSS
On voit que c'est assez facile de faire ça au final, il faut juste prendre le temps de définir le template de notre grille correctement.
Conclusion🔗
Je n'ai clairement pas fait un guide ultime de layout en CSS, certains en lignes le fond clairement bien mieux que moi ! Je vous montre juste quelques exemples de layout qu'on peut réaliser simplement, mais qui demande de comprendre comment fonctionne flex et grid.
Il reste beaucoup de chose à explorer : toutes les propriétés de flex/grid que je n'ai pas utilisé, l'imbrication de flex, l'imbrication de grid, les sub-grid, le mix flex / grid, etc. J'espère juste vous avoir montré qu'avec du CSS moderne on n'a pas besoin d'imbriquer des <div> dans tous sens, d'avoir des class sur tous les éléments, d'avoir un outil "utility-first" à la Tailwind, ou n'importe quoi d'autres pour faire un layout, juste un peu de CSS et se poser sur ce qu'on veut faire.
Source :
- Code source du projet
- Cheat sheet Flexbox
- Cheat sheet Grid
- CSS Flex (mdn)
- CSS Grid layout (mdn)
- CSS grid-template (mdn)
- CSS grid-area (mdn)
- CSS grid-row (mdn)
- CSS grid-column (mdn)
- CSS grid-template-columns (mdn)
- CSS grid-template-rows (mdn)
Crédit photo : Générée via Mistral AI avec le prompt suivant :
A beautifully detailed 16:9 illustration of a Japanese bento box, divided into compartments that visually represent CSS layout techniques (flex, grid, bento, and stairs). Each compartment contains a cute animal: a red panda in a larger, prominent compartment (symbolizing a hero section), an axolotl in a medium-sized compartment (symbolizing a sidebar or secondary content), and a cat in a smaller compartment (symbolizing a footer or tertiary content). The bento box itself is intricately designed, with wooden textures and soft shadows, evoking the warmth and charm of Studio Ghibli’s art style.
The compartments are organized to reflect different CSS layouts:
- The top row uses a flex layout, with equal-sized compartments.
- The middle row uses a grid layout, with varying compartment sizes.
- The bottom row uses a bento layout, with asymmetrical, nested compartments.
- The right side features a stairs-like arrangement, with overlapping compartments.
Around the bento box, small CSS icons and symbols (like
, flexbox, grid, and curly braces {}) float gently, adding a whimsical touch. The background is a dark, minimalist gradient with subtle glowing highlights, reminiscent of a cozy night scene. The animals are expressive and playful: the red panda is holding a tiny HTML tag, the axolotl is peeking out from behind a grid symbol, and the cat is curled up next to a flexbox icon. The lighting is soft and warm, casting gentle shadows and creating a cozy, inviting atmosphere. The overall mood is magical, charming, and creative, celebrating the art of CSS and web design.