Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

5.2 — CSS moderne

Débutant 35 min prérequis : axe 5.1 lu

🎯 Objectif : maîtriser CSS au point que Flexbox et Grid sont des réflexes, que tu connais le piège de la cascade, et que tu construis des thèmes cohérents avec des variables.

À l'issue de cet axe, tu sauras :

  • Maîtriser sélecteurs, spécificité, cascade, héritage
  • Comprendre le box model et le contexte de mise en forme
  • Mettre en page avec Flexbox et Grid en réflexe
  • Construire un design responsive mobile-first sans framework
  • Utiliser variables CSS, container queries, :has()
  • Créer des animations performantes (transform, opacity)
/* Type */
button { ... }
/* Classe */
.btn-primary { ... }
/* Id (à éviter en styling, c'est trop spécifique) */
#hero { ... }
/* Attribut */
[type="email"] { ... }
[data-state="open"] { ... }
/* Combinateurs */
nav a { ... } /* descendant */
nav > a { ... } /* enfant direct */
h2 + p { ... } /* frère adjacent */
h2 ~ p { ... } /* tous les frères suivants */
/* Pseudo-classes */
a:hover { ... }
input:focus-visible { ... } /* focus seulement au clavier */
button:disabled { ... }
li:nth-child(odd) { ... }
li:nth-child(2n+1) { ... }
:not(.disabled) { ... }
/* Pseudo-éléments */
p::first-letter { ... }
p::before { content: ''; }
::selection { background: yellow; }
/* :where() — spécificité de 0 (prend la spécificité de l'élément) */
:where(article, section, aside) h2 { color: blue; }
/* :is() — comme :where() mais prend la plus haute spécificité */
:is(article, section) h2 { color: blue; }
/* :has() — relation parent → enfant ! (champion 2024+) */
article:has(img) { padding: 1rem; } /* article QUI contient une img */
form:has(input:invalid) { border: 2px solid red; }
/* Focus dans un conteneur */
.card:has(:focus) { outline: 2px solid blue; }

Quand 2 règles s’appliquent, la plus spécifique gagne. Score :

[id, class/attr/pseudo-class, type/pseudo-element]
SélecteurSpécificité
*0,0,0
div0,0,1
.btn0,1,0
nav a0,0,2
nav .link0,1,1
#header1,0,0
style="..." (inline)1,0,0,0
!importantau-dessus de tout

Règle pratique : si tu écris !important, c’est souvent qu’il y a un problème de design CSS. Refactore plutôt que d’enchaîner les !important.

Quand plusieurs règles ont la même spécificité, la dernière déclarée gagne. C’est pour ça que les resets / utility classes vont en bas.

Certaines propriétés s’héritent du parent (color, font-family, line-height), d’autres pas (margin, padding, border). Pour forcer l’héritage : inherit.

.parent { color: red; }
.parent button { color: inherit; } /* hérite de red, sinon le bouton aurait son propre noir natif */
flowchart TD
    Margin["margin (espace EXTÉRIEUR — autour de la border)"]
    Border["border"]
    Padding["padding (espace INTÉRIEUR — entre border et contenu)"]
    Content["content (le texte, les enfants…)"]
    Margin --> Border --> Padding --> Content
Le box model — ce que tu vois et ce que prend chaque élément
*, *::before, *::after {
box-sizing: border-box;
}

Avec ça : width: 200px inclut padding et border. Sans (mode content-box historique), un padding de 20px ferait dépasser à 240px → galère.

À mettre dans toute base CSS de projet.

Pour les mises en page en une dimension (ligne ou colonne).

.parent {
display: flex;
/* axe principal (horizontal par défaut) */
flex-direction: row | row-reverse | column | column-reverse;
/* alignement sur l'axe principal */
justify-content: flex-start | center | flex-end | space-between | space-around | space-evenly;
/* alignement sur l'axe secondaire */
align-items: stretch | flex-start | center | flex-end | baseline;
/* retour à la ligne si pas assez de place */
flex-wrap: nowrap | wrap;
/* espace entre items */
gap: 1rem;
}
.enfant {
flex: 1; /* grandit proportionnellement à l'espace dispo */
flex: 0 0 auto; /* taille fixe, ne grandit ni ne réduit */
flex: 1 1 200px; /* base 200px, grandit, réduit */
}
/* Centrer parfaitement (le mythique) */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* Header avec logo à gauche, nav à droite */
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Cards qui prennent tout l'espace dispo en s'étirant */
.cards {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.cards > * { flex: 1 1 250px; } /* min 250px, sinon flexibles */
/* Push un élément en bas (footer dans une carte) */
.card { display: flex; flex-direction: column; }
.card .actions { margin-top: auto; }

Pour les mises en page en 2 dimensions (lignes ET colonnes).

.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 3 colonnes, ratios */
grid-template-columns: repeat(12, 1fr); /* 12 colonnes égales */
grid-template-columns: 200px 1fr 200px; /* sidebar fixe, contenu fluide */
grid-template-rows: 60px 1fr 60px; /* header / main / footer */
gap: 1rem;
}
/* Aire nommée — la magie du grid */
.layout {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 60px;
min-height: 100vh;
}
.layout > header { grid-area: header; }
.layout > nav { grid-area: nav; }
.layout > main { grid-area: main; }
.layout > footer { grid-area: footer; }
/* Cards responsive sans media queries (auto-fit) */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
/* Sur grand écran : autant de colonnes de 250px+ que possible
Sur mobile : 1 colonne automatiquement */
/* Span sur plusieurs colonnes */
.featured { grid-column: span 2; }
.full { grid-column: 1 / -1; } /* du début à la fin */
FlexboxGrid
Dimensions1 (ligne ou col)2 (ligne ET col)
Cas typeToolbar, header, navigationLayout de page, dashboard, galerie
ItemsLe contenu dicte la tailleLa grille dicte les positions

Conseil : Grid pour le layout, Flexbox pour les composants. Tu peux nester l’un dans l’autre sans souci.

Piège réel rencontré — Image sans width+height plombe le CLS Performance

🩹 Symptôme : Lighthouse CLS = 0.4 (cible ≤ 0.1). Le contenu en dessous de l’image saute quand elle charge.

🔍 Cause : sans width et height (ou aspect-ratio), le navigateur ne sait pas combien de place réserver. Quand l’image charge, le contenu en dessous est poussé.

🩺 Fix : toujours fournir les dimensions intrinsèques.

<img src="/hero.jpg" width="1024" height="600" alt="..." />

Le ratio est préservé même avec max-width: 100%; height: auto; en CSS.

🧠 Leçon : sur 9 sites/10, width+height manquants sont la 1ère cause de mauvais CLS. Linter HTML ou Lighthouse en CI bloquant te le rappelle automatiquement.

Voir aussi tous les pièges sur /pieges/.

/* Styles mobile (par défaut) */
.container { padding: 1rem; }
.card { width: 100%; }
/* Tablet et plus */
@media (min-width: 768px) {
.container { padding: 2rem; }
.card { width: calc(50% - 1rem); }
}
/* Desktop */
@media (min-width: 1024px) {
.container { max-width: 1200px; margin: 0 auto; }
.card { width: calc(33% - 1rem); }
}

3 breakpoints suffisent généralement. Ne traque pas chaque appareil.

h1 {
/* 2rem min, 5vw préféré, 4rem max */
font-size: clamp(2rem, 5vw, 4rem);
}
.section {
padding-block: clamp(2rem, 8vw, 8rem);
}

clamp(min, ideal, max) adapte la valeur sans media queries. Plus fluide qu’un changement abrupt à un breakpoint.

Le composant s’adapte à son conteneur, pas à la fenêtre :

.card-list {
container-type: inline-size;
}
.card { display: block; }
@container (min-width: 600px) {
.card { display: flex; }
}

Ainsi une card peut être en colonne dans une sidebar étroite et en ligne dans le main, sur la même page.

:root {
--color-primary: #2563eb;
--color-bg: #ffffff;
--space-md: 1rem;
--radius-md: 0.5rem;
}
[data-theme='dark'] {
--color-bg: #111827;
}
.btn {
background: var(--color-primary);
padding: var(--space-md);
border-radius: var(--radius-md);
}
/* Avec fallback */
.foo { color: var(--accent, #333); }

Bénéfices :

  • Changement de thème en 1 ligne (data-theme="dark").
  • Cohérence : impossible d’avoir 47 nuances de bleu par accident.
  • Lisibilité : var(--space-md) est plus parlant que 0.75rem.

Depuis Tailwind v4 (sortie en janvier 2025), la configuration des design tokens se fait directement en CSS via la directive @theme, plus dans un tailwind.config.js :

@import 'tailwindcss';
@theme {
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--font-sans: 'Inter', system-ui, sans-serif;
--spacing-tight: 0.25rem;
--radius-md: 0.5rem;
}

Tailwind génère automatiquement les utilities correspondantes (bg-primary, text-primary-hover, etc.) — ton CSS et ton design system sont au même endroit. C’est la pratique recommandée 2026.

:root {
--color-bg: #fff;
--color-text: #111;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #111;
--color-text: #f9f9f9;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
}
.btn {
background: blue;
transition: background 200ms ease, transform 150ms ease;
}
.btn:hover {
background: darkblue;
transform: translateY(-2px);
}

Règle de perf : anime transform et opacity (composite seulement). Évite width, height, top, left (déclenchent layout — voir axe 2.4).

@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.loading {
animation: pulse 2s infinite ease-in-out;
}
@view-transition {
navigation: auto;
}

Active des transitions natives entre pages (Astro, SvelteKit le supportent).

.card { ... } /* Block */
.card__title { ... } /* Element (enfant) */
.card--featured { ... } /* Modifier (variante) */
.card__title--large { ... } /* Element + Modifier */
<article class="card card--featured">
<h2 class="card__title card__title--large">...</h2>
</article>

✅ Pas de conflit de nom global. ❌ Verbeux.

<article class="rounded-lg border border-gray-200 p-6 hover:shadow-md transition">
<h2 class="text-2xl font-bold mb-2">Titre</h2>
</article>

✅ Vitesse de prototypage, design system par défaut, supprime le bikeshedding sur les noms. ❌ HTML chargé visuellement, courbe d’apprentissage des classes.

  • Petit projet / prototype : Tailwind CSS, sans hésiter.
  • Design system custom : CSS modules ou plain CSS avec variables et BEM.
  • Mix : tu peux faire un design system avec Tailwind (@apply ou CVA) et garder la cohérence.
/* Modern CSS Reset (inspiré de Josh Comeau) */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html, body { height: 100%; }
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
font-family: system-ui, sans-serif;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
#root, #__next {
isolation: isolate;
}

À mettre en haut de ta CSS sur tout projet from-scratch. Fait disparaître 80 % des bugs CSS classiques.

Tu veux un layout : sidebar 200px à gauche + contenu fluide à droite, qui passe en colonne sur mobile. Outil idéal ?
Tu veux animer un menu qui passe de width: 0 à width: 300px. L'animation est saccadée. Solution ?
Quelle ligne fait des cards responsive sans aucune media query ?

Suite : 5.3 — Accessibilité — passer du bon HTML/CSS à un site vraiment utilisable par tout le monde.