Niveau 26 — Le CSS Moderne

Le CSS Moderne

Le CSS évolue à grande vitesse. Découvrez les nouvelles fonctionnalités natives qui révolutionnent l'écriture de styles : CSS Nesting, Container Queries, @layer, :has(), @scope, Logica Properties, Scroll Snap, les nouvelles couleurs, @starting-style et le Popover API.

1. Comprendre le CSS moderne

En une phrase : Le CSS moderne ajoute des fonctionnalités natives au langage qui éliminent le besoin de préprocesseurs, simplifient la cascade, améliorent la maintenabilité et rendent possible des patterns auparavant nécessitant JavaScript.

Le CSS a considérablement évolué ces dernières années. Les navigateurs ajoutent des fonctionnalités qui rendent le code plus court, plus maintenable et plus puissant. Voici ce que nous allons couvrir :

NOUVELLES FONCTIONNALITÉS CSS 2023-2026
═══════════════════════════════════════════

  🔧 ORGANISATION        📐 LAYOUT & RÉPONSIVE
  ──────────────         ─────────────────────
  • CSS Nesting          • Container Queries
  • @layer               • Scroll Snap
  • @scope               • Logical Properties

  🎨 COULEURS            🧩 SÉLECTEURS
  ────────────           ──────────────
  • oklch / oklab        • :has()
  • color-mix()          • @starting-style
  • accent-color

  🪟 COMPOSANTS NATIFS   🌗 THÉMES
  ─────────────────      ──────────
  • Popover API          • color-scheme
  • @starting-style

Panorama des nouvelles fonctionnalités CSS

Table de support navigateur

Fonctionnalité Chrome Firefox Safari Date de support
CSS Nesting 120+ ✓ 117+ ✓ 17.2+ ✓ 2023
@layer 99+ ✓ 97+ ✓ 15.4+ ✓ 2022
Container Queries 105+ ✓ 110+ ✓ 16+ ✓ 2023
:has() 105+ ✓ 121+ ✓ 15.4+ ✓ 2023
@scope 118+ ✓ ⚠ Partiel 2024
Logical Properties 69+ ✓ 55+ ✓ 12.1+ ✓ 2020
Scroll Snap 69+ ✓ 62+ ✓ 11+ ✓ 2020
accent-color 93+ ✓ 92+ ✓ 15.4+ ✓ 2021
color-scheme 81+ ✓ 67+ ✓ 12.1+ ✓ 2020
oklch / oklab 111+ ✓ 113+ ✓ 15.4+ ✓ 2023
color-mix() 111+ ✓ 113+ ✓ 16.2+ ✓ 2023
@starting-style 117+ ✓ ⚠ Partiel 17.5+ ✓ 2024
Popover API 114+ ✓ 125+ ✓ 17+ ✓ 2024

2. Syntaxe détaillée

2.1 CSS Nesting (imbriquement natif)

Définition : Le CSS Nesting permet d'imbriquer des sélecteurs à l'intérieur d'autres sélecteurs, exactement comme Sass. Cela rend le code plus court, plus lisible et plus organisé, tout en éliminant le besoin d'un préprocesseur pour cette fonctionnalité.

Syntaxe de base

/* AVANT — Sans nesting (CSS classique) */
.card { background: white; border-radius: 12px; }
.card__title { font-weight: 700; color: #1e293b; }
.card__text { color: #64748b; line-height: 1.6; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card--featured { border-color: #2563eb; }
.card--featured .card__title { color: #2563eb; }

/* APRÈS — Avec CSS Nesting */
.card {
  background: white;
  border-radius: 12px;

  /* Nesting d'éléments (comme BEM) */
  &__title {
    font-weight: 700;
    color: #1e293b;
  }

  &__text {
    color: #64748b;
    line-height: 1.6;
  }

  /* Nesting de pseudo-classes */
  &:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  /* Nesting de modificateurs */
  &--featured {
    border-color: #2563eb;

    & .card__title {
      color: #2563eb;
    }
  }
}

Le sélecteur &

/* & fait référence au sélecteur parent */

.btn {
  padding: 0.5rem 1rem;

  /* & = .btn */
  &:hover { background: darker; }
  &:focus { box-shadow: ring; }
  &:active { transform: scale(0.98); }

  /* & permet de remonter dans l'arbre */
  .sidebar & {
    padding: 0.25rem 0.5rem;
  }
  /* .sidebar .btn { ... } */
}

/* Nesting de media queries */
.card {
  padding: 1rem;

  @media (min-width: 768px) {
    padding: 2rem;
  }
}

/* Nesting de :has() */
.form-group {
  margin-bottom: 1rem;

  &:has(input:focus) {
    background: #eff6ff;
  }
}

Nesting des at-rules

/* Les at-rules s'imbriquent directement */
.component {
  color: #333;

  @media (min-width: 768px) {
    color: #111;
    font-size: 1.1rem;
  }

  @supports (backdrop-filter: blur(10px)) {
    background: rgba(255,255,255,0.8);
    backdrop-filter: blur(10px);
  }
}
Compatibilité : Le CSS Nesting natif est supporté par tous les navigateurs modernes depuis début 2024. Il ne nécessite plus le préfixe & devant les pseudo-classes depuis Chrome 120.

2.2 @layer (Cascade Layers)

Définition : @layer permet de créer des couches de cascade explicites, donnant un contrôle total sur l'ordre de priorité des styles. C'est la réponse du CSS aux problèmes de spécificité et aux conflits entre bibliothèques.
/* Déclarer des couches dans l'ordre de priorité */
@layer base, components, utilities;

/* Couche 1 : Styles de base (priorité la plus basse) */
@layer base {
  body { font-family: sans-serif; color: #333; }
  h1 { font-size: 2rem; margin: 0; }
}

/* Couche 2 : Composants (priorité moyenne) */
@layer components {
  .card { padding: 1rem; border: 1px solid #e2e8f0; }
  .btn { padding: 0.5rem 1rem; border-radius: 8px; }
}

/* Couche 3 : Utilities (priorité la plus haute) */
@layer utilities {
  .hidden { display: none !important; }
  .text-center { text-align: center; }
}

/* Les styles non-couchés ont la priorité la plus haute */
.unlayered {
  color: red; /* Écrase tout ce qui est dans les couches */
}
base — Priorité la plus basse reset, éléments nus
components — Priorité moyenne card, btn, nav
utilities — Priorité la plus haute hidden, text-center
/* Ordre = Priorité : la dernière couche déclare gagne */
@layer reset, base, components, utilities;

/* Même si .card a une spécificité plus élevée,
   utilities gagne toujours car c'est la dernière couche */

/* Importer des bibliothèques dans des couches */
@layer reset, third-party, my-components, my-utilities;

@import url('normalize.css') layer(reset);
@import url('some-lib.css') layer(third-party);

@layer my-components {
  /* Vos composants — plus prioritaire que la lib */
}

@layer my-utilities {
  /* Vos utilities — priorité maximale */
}

2.3 Container Queries

Définition : Les Container Queries permettent de styliser un élément en fonction de la taille de son conteneur parent plutôt que de la taille de la fenêtre. C'est la révolution du responsive design — un composant s'adapte à son contexte, pas à l'écran.
/* Définir un conteneur */
.card-wrapper {
  container-type: inline-size;   /* Réagit à la largeur */
  container-name: card;          /* Nom pour le ciblage */
}

/* ou avec le raccourci */
.card-wrapper {
  container: card / inline-size;
}

/* Styles conditionnels basés sur le conteneur */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;   /* horizontal si le conteneur est large */
  }
  .card__image {
    width: 200px;
  }
}

@container card (max-width: 399px) {
  .card {
    flex-direction: column; /* vertical si le conteneur est étroit */
  }
}

/* Container query units */
@container card (min-width: 400px) {
  .card__title {
    font-size: 2cqi; /* 2% de la largeur du conteneur */
  }
}

Types de conteneurs

/* inline-size — réagit à la largeur (le plus courant) */
.card-container { container-type: inline-size; }

/* size — réagit à la largeur ET la hauteur */
.full-container { container-type: size; }

/* Conteneur normal — pas de containment */
.parent { container-type: normal; }

Unités de container query

/* cqi — container query inline (largeur) */
/* cqb — container query block (hauteur) */
/* cqmin — le plus petit des deux */
/* cqmax — le plus grand des deux */

.titre {
  font-size: 3cqi;     /* 3% de la largeur du conteneur */
  line-height: 1.2cqb; /* 120% de la hauteur du conteneur */
}
Démonstration : Container Queries (redimensionnez la zone)
Carte adaptative
Glissez le bord droit pour redimensionner. La carte passe en horizontal au-delà de 400px.

2.4 Le sélecteur :has()

Définition : :has() est le "sélecteur parent" tant attendu. Il permet de cibler un élément en fonction du contenu de ses enfants. C'est l'un des ajouts les plus puissants à l'histoire du CSS.
/* Cibler un formulaire qui contient un input en erreur */
.form:has(.input--error) {
  border-color: red;
}

/* Cibler une carte qui contient une image */
.card:has(.card__image) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

/* Cibler un élément qui suit un autre */
h2:has(+ p) {
  /* h2 suivi immédiatement d'un paragraphe */
  margin-bottom: 0.5rem;
}

/* Cibler un parent quand un enfant est focusé */
.form-group:has(input:focus) {
  background: #eff6ff;
}

/* Conditions complexes */
.page:has(.sidebar):has(.main-content) {
  display: grid;
  grid-template-columns: 250px 1fr;
}
Démonstration : :has() — Interagissez avec les inputs

Le fond du groupe d'input change bleu quand l'input est focusé, grâce à :has(input:focus).

/* Le parent change d'apparence quand l'enfant est focusé */
.form-group {
  transition: background 0.2s, padding 0.2s;
}

.form-group:has(input:focus) {
  background: #eff6ff;
  padding: 0.5rem;
  margin: -0.5rem;
  border-radius: 10px;
}

/* Le label change de couleur quand l'input n'est pas vide */
.form-group:has(input:not(:placeholder-shown)) label {
  color: #2563eb;
  font-weight: 700;
}

2.5 @scope

Définition : @scope crée un scope CSS — un domain de styles limité à un sous-ensemble du DOM. Les styles ne "fuient" pas du scope et ne peuvent pas être affectés par des styles extérieurs. C'est la modularité parfaite.
/* Définir un scope avec limites */
@scope (.card) to (.card__footer) {
  /* Ces styles ne s'appliquent qu'aux éléments
     inside .card mais PAS dans .card__footer */

  p { color: #475569; }
  a { color: #2563eb; text-decoration: underline; }
}

/* Les liens dans .card__footer ne seront PAS bleus */
/* Les liens dans .card__body seront bleus */

/* Scope sans limite basse — tout le sous-arbre */
@scope (.sidebar) {
  .nav-link { color: white; }
  /* S'applique à TOUS les .nav-link dans .sidebar */
}

/* Scope multiple */
@scope (.theme-a) {
  /* Styles uniquement pour les éléments dans .theme-a */
  button { background: blue; }
}

@scope (.theme-b) {
  /* Styles uniquement pour les éléments dans .theme-b */
  button { background: red; }
}
@scope résout le problème des styles qui "fuient" d'un composant. C'est particulièrement utile dans les design systems où plusieurs versions d'un même composant coexistent.

2.6 Logical Properties (Propriétés logiques)

Définition : Les propriétés logiques remplacent left/right et top/bottom par des concepts de block (vertical) et inline (horizontal), rendant le CSS nativement compatible avec les langues s'écrivant de droite à gauche (arabe, hébreu).
/* AVANT — Propriétés physiques */
.el {
  margin-left: 1rem;
  margin-right: 2rem;
  padding-top: 0.5rem;
  padding-bottom: 1rem;
  border-left: 3px solid blue;
  text-align: left;
  float: right;
}

/* APRÈS — Propriétés logiques */
.el {
  margin-inline-start: 1rem;   /* gauche en LTR, droite en RTL */
  margin-inline-end: 2rem;
  padding-block-start: 0.5rem; /* haut */
  padding-block-end: 1rem;     /* bas */
  border-inline-start: 3px solid blue;
  text-align: start;
  float: inline-end;
}

/* Raccourcis */
.el {
  margin-inline: 1rem 2rem;      /* start et end */
  margin-block: 0.5rem 1rem;     /* start et end */
  padding-inline: 1rem;          /* les deux côtés */
  padding-block: 0.5rem;         /* haut et bas */
  inset-inline: 0;               /* left + right en position absolute */
  inset-block: 0;                /* top + bottom */
  inset: 0;                      /* raccourci pour les 4 */
}
CORRESPONDANCE PHYSIQUE → LOGIQUE
════════════════════════════════════

  PHYSIQUE          LOGIQUE              DESCRIPTION
  ────────────────  ──────────────────   ──────────────────
  margin-left       margin-inline-start  Début inline
  margin-right      margin-inline-end    Fin inline
  margin-top        margin-block-start   Début block
  margin-bottom     margin-block-end     Fin block

  padding-left      padding-inline-start
  padding-right     padding-inline-end
  padding-top       padding-block-start
  padding-bottom    padding-block-end

  border-left       border-inline-start
  border-right      border-inline-end
  border-top        border-block-start
  border-bottom     border-block-end

  left (position)   inset-inline-start
  right (position)  inset-inline-end
  top (position)    inset-block-start
  bottom (position) inset-block-end

  width             inline-size
  height            block-size
  min-width         min-inline-size
  max-width         max-inline-size

  text-align: left  text-align: start
  text-align: right text-align: end

Guide de correspondance des propriétés physiques vers logiques

2.7 Scroll Snap

Définition : Le Scroll Snap permet de créer un défilement "magnétique" où le conteneur s'arrête naturellement sur des points de snap définis. Idéal pour des carrousels, galeries et sections de présentation.
/* Conteneur — type de snap */
.scroll-container {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory; /* ou x proximity */
  gap: 1rem;
  padding: 1rem;
}

/* Éléments — où snap */
.snap-item {
  flex: 0 0 300px;
  scroll-snap-align: start; /* start, center, end */
}

/* Scroll smooth */
.smooth-scroll {
  scroll-behavior: smooth;
}

/* Scroll snap vertical */
.vertical-snap {
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  height: 400px;
}

.vertical-snap section {
  scroll-snap-align: start;
  min-height: 100%;
}
Démonstration : Scroll Snap (scrollez horizontalement)
🎨
Design
Créez des interfaces magnifiques
Performance
Des sites ultra-rapides
📱
Responsive
Adapté à tous les écrans
Accessibilité
Pour tout le monde
🔒
Sécurité
Protégez vos utilisateurs
🚀
Déploiement
Shippez en confiance

2.8 accent-color

Définition : accent-color définit la couleur d'accentuation pour les éléments de formulaire natifs (checkbox, radio, range, progress). Un seul style change la couleur de tous les contrôles.
/* Accent global */
:root {
  accent-color: #2563eb;
}

/* Accent par élément */
.checkbox { accent-color: #16a34a; }
.radio { accent-color: #7c3aed; }
.progress { accent-color: #f59e0b; }

/* Avec une classe */
.theme-primary { accent-color: #2563eb; }
.theme-success { accent-color: #16a34a; }
.theme-danger { accent-color: #dc2626; }
Démonstration : accent-color

2.9 color-scheme

Définition : color-scheme indique au navigateur les schémas de couleurs supportés, permettant de styliser les éléments natifs (scrollbar, form controls, etc.) pour le thème sombre automatiquement.
/* Déclarer les schémas supportés */
:root {
  color-scheme: light dark;
}

/* Le navigateur adaptera automatiquement :
   - La scrollbar
   - Les contrôles de formulaire natifs
   - Le sélecteur de date
   - Le remplissage automatique
*/

/* Thème sombre global */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
  }
  body {
    background: #0f172a;
    color: #f1f5f9;
  }
}

/* Forcer un thème spécifique */
.force-light { color-scheme: light; }
.force-dark { color-scheme: dark; }

2.10 Nouvelles fonctions de couleur

oklch() est le nouveau modèle de couleur recommandé. Il est plus intuitif que HSL (luminosité perceptuellement uniforme) et permet de créer des palettes plus harmonieuses. color-mix() permet de mélanger des couleurs directement en CSS.

oklch — Luminosité chromatique perçue

/* oklch(luminosité chroma hue) */
/* Luminosité : 0 (noir) à 1 (blanc) */
/* Chroma : 0 (gris) à ~0.4 (saturated) */
/* Hue : 0-360 (teinte en degrés) */

:root {
  --blue: oklch(0.55 0.2 250);
  --green: oklch(0.6 0.18 145);
  --purple: oklch(0.5 0.2 300);
  --red: oklch(0.55 0.2 25);

  /* Plus facile de créer des nuances */
  --blue-50: oklch(0.95 0.05 250);
  --blue-100: oklch(0.9 0.08 250);
  --blue-500: oklch(0.55 0.2 250);
  --blue-900: oklch(0.25 0.15 250);
}

/* Comparaison avec HSL */
.hsl-blue { color: hsl(220, 80%, 50%); }   /* Pas uniforme */
.oklch-blue { color: oklch(0.55 0.2 250); } /* Uniforme */

color-mix() — Mélanger des couleurs

/* Mélanger 2 couleurs avec un pourcentage */
.btn-primary {
  background: color-mix(in oklch, #2563eb 100%, black 20%);
  /* Le bleu assombri de 20% */
}

.btn-primary:hover {
  background: color-mix(in oklch, #2563eb 100%, black 40%);
  /* Encore plus sombre */
}

/* Créer un fond semi-transparent */
.overlay {
  background: color-mix(in srgb, #000 50%, transparent);
  /* Noir à 50% d'opacité */
}

/* Mélanger des couleurs pour des dégradés */
.gradient {
  background: color-mix(in oklch, blue 50%, red 50%);
  /* Violet obtenu en mélangeant bleu et rouge */
}

/* Créer des variantes de couleur */
:root {
  --primary: #2563eb;
  --primary-light: color-mix(in oklch, var(--primary) 80%, white);
  --primary-dark: color-mix(in oklch, var(--primary) 80%, black);
  --primary-subtle: color-mix(in oklch, var(--primary) 10%, white);
}
Démonstration : oklch et color-mix()
50oklch
100oklch
300oklch
500oklch
700oklch
900oklch
blue + 20% black
blue + 20% white
blue 50% + red 50%

2.11 @starting-style

Définition : @starting-style définit les styles initiaux d'un élément avant qu'il n'entre dans le DOM (ou devienne visible), permettant des animations d'entrée sans JavaScript.
/* Élément qui apparaît avec une animation */
.dialog {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.3s, transform 0.3s;
}

/* Styles quand l'élément apparaît pour la première fois */
@starting-style {
  .dialog {
    opacity: 0;
    transform: translateY(20px);
  }
}

/* Pour un popover */
[popover]:popover-open {
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scale(0.95);
  }
}

/* Pour un élément qui change de display:none à display:block */
.tooltip {
  display: block;
  opacity: 1;
  transition: opacity 0.2s, display 0.2s allow-discrete;
}

@starting-style {
  .tooltip {
    opacity: 0;
  }
}

2.12 Popover API

Définition : L'API Popover est un attribut HTML natif qui transforme un élément en popover (overlay léger) sans JavaScript. Le navigateur gère l'ouverture, la fermeture, le focus trap et l'overlay automatiquement.
<!-- HTML — Popover natif -->
<button popovertarget="mon-popover">Ouvrir</button>

<div id="mon-popover" popover>
  <h3>Titre du popover</h3>
  <p>Contenu du popover...</p>
  <button popovertarget="mon-popover" popovertargetaction="hide">
    Fermer
  </button>
</div>

<!-- Le popover est :
     - masqué par défaut (display:none)
     - positionné par rapport au trigger
     - gère le focus automatiquement
     - se ferme avec Escape
     - a un ::backdrop -->

<!-- CSS pour le popover -->
<style>
[popover] {
  padding: 1.5rem;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.12);
  max-width: 300px;
}

/* Animation d'entrée */
[popover]:popover-open {
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.2s, transform 0.2s,
              display 0.2s allow-discrete;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scale(0.95);
  }
}
</style>
Démonstration : Popover API natif

Popover natif

Ceci est un popover géré par le navigateur. Appuyez sur Escape pour fermer.

3. Visualisation

3.1 Résumé — Avant/Après CSS moderne

Avant (CSS classique / Sass)
/* Nesting avec Sass */ .card { background: white; border-radius: 12px; &__title { font-weight: 700; } &__text { color: #64748b; } &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } &--featured { border-color: blue; } } /* Spécificité galère */ .sidebar .card .title { } #main .card > .title { } /* Pas de containment */ .card { /* pas de container */ } @media (min-width: 400px) { /* responsive global, pas par composant */ }
Après (CSS moderne)
/* Nesting natif — même syntaxe */ .card { background: white; border-radius: 12px; &__title { font-weight: 700; } &__text { color: #64748b; } &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } &--featured { border-color: blue; } } /* :has() — pas de sélecteur parent */ .form:has(input:focus) { } /* Container Queries */ .card-wrap { container-type: inline-size; } @container (min-width: 400px) { .card { flex-direction: row; } } /* @layer — cascade contrôlée */ @layer reset, base, components, utils;

4. Exemples progressifs

4.1 Composant Card avec CSS Nesting

/* Card moderne avec CSS Nesting natif */
.card {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: box-shadow 0.3s, transform 0.3s;

  &:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
  }

  &__image {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
  }

  &__body {
    padding: var(--space-6);
  }

  &__title {
    font-size: var(--text-xl);
    font-weight: 700;
    margin-bottom: var(--space-2);
    color: var(--color-text);

    &:hover {
      color: var(--color-primary);
    }
  }

  &__text {
    color: var(--color-text-secondary);
    line-height: 1.6;
    margin-bottom: var(--space-4);
  }

  &__footer {
    padding: var(--space-4) var(--space-6);
    border-top: 1px solid var(--color-border);
    display: flex;
    gap: var(--space-3);
  }

  &--featured {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 1px var(--color-primary);

    .card__title {
      color: var(--color-primary);
    }
  }

  &--compact {
    .card__body {
      padding: var(--space-4);
    }
  }

  /* Responsive via nesting de media query */
  @media (min-width: 768px) {
    .card__body {
      padding: var(--space-8);
    }
  }
}

4.2 Composant avec Container Queries

/* Layout adaptatif de carte */
.product-card-wrapper {
  container-type: inline-size;
  container-name: product;
}

.product-card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
  background: white;
  border-radius: 12px;
  border: 1px solid #e2e8f0;

  &__image {
    width: 100%;
    aspect-ratio: 4/3;
    object-fit: cover;
    border-radius: 8px;
  }

  &__content {
    flex: 1;
  }

  &__title {
    font-size: 1.1rem;
    font-weight: 700;
    margin-bottom: 0.5rem;
  }

  &__price {
    font-size: 1.25rem;
    font-weight: 700;
    color: #2563eb;
  }

  &__btn {
    width: 100%;
    margin-top: 1rem;
  }
}

/* Layout horizontal quand le conteneur est large */
@container product (min-width: 500px) {
  .product-card {
    flex-direction: row;
    align-items: center;
  }

  .product-card__image {
    width: 200px;
    aspect-ratio: 1;
    flex-shrink: 0;
  }

  .product-card__btn {
    width: auto;
  }
}

/* Layout compact pour les très petits conteneurs */
@container product (max-width: 250px) {
  .product-card__title {
    font-size: 0.9rem;
  }

  .product-card__price {
    font-size: 1rem;
  }
}

4.3 Thème avec color-scheme + oklch

/* Thème moderne avec color-scheme et oklch */
:root {
  color-scheme: light dark;

  /* Palette oklch */
  --color-primary: oklch(0.55 0.2 250);
  --color-primary-hover: oklch(0.45 0.2 250);
  --color-bg: oklch(0.98 0.005 250);
  --color-text: oklch(0.2 0.02 250);
  --color-surface: oklch(0.96 0.005 250);
  --color-border: oklch(0.88 0.005 250);

  /* Couleurs dérivées avec color-mix */
  --color-primary-light: color-mix(in oklch, var(--color-primary) 15%, transparent);
  --color-primary-dark: color-mix(in oklch, var(--color-primary) 80%, black);
}

/* Adaptation automatique en mode sombre */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: oklch(0.15 0.01 250);
    --color-text: oklch(0.9 0.01 250);
    --color-surface: oklch(0.2 0.01 250);
    --color-border: oklch(0.3 0.01 250);
    --color-primary: oklch(0.65 0.2 250);
  }
}

4.4 Layout avec Logical Properties

/* Layout RTL-ready avec Logical Properties */
.article {
  padding-block: 2rem;
  padding-inline: 1.5rem;
  max-width: 700px;
  margin-inline: auto;
}

.article__title {
  font-size: 2rem;
  margin-block-end: 1rem;
  border-block-end: 2px solid var(--color-border);
  padding-block-end: 0.5rem;
}

.article__meta {
  display: flex;
  gap: 1rem;
  margin-block-end: 1.5rem;
  color: var(--color-text-secondary);
  font-size: 0.85rem;
}

.article__content {
  font-size: 1.05rem;
  line-height: 1.8;
}

.article__content p {
  margin-block-end: 1.5rem;
}

.article__sidebar {
  position: sticky;
  inset-inline-end: 1rem;
  inset-block-start: 1rem;
}

/* Fonctionne automatiquement en RTL */
html[dir="rtl"] .article {
  /* Pas besoin de changer quoi que ce soit ! */
  /* padding-inline gère left/right automatiquement */
}

4.5 Popover animé avec @starting-style

<!-- HTML -->
<button popovertarget="menu">Menu</button>
<div id="menu" popover>
  <nav>
    <a href="#">Accueil</a>
    <a href="#">Produits</a>
    <a href="#">Contact</a>
  </nav>
</div>
/* CSS */
[popover] {
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.12);
}

[popover]:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  transition:
    opacity 0.3s ease,
    transform 0.3s ease,
    display 0.3s allow-discrete,
    overlay 0.3s allow-discrete;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: translateY(-10px) scale(0.95);
  }
}

/* Le::backdrop pour l'overlay */
[popover]::backdrop {
  background: rgba(0,0,0,0.2);
  transition: opacity 0.3s, display 0.3s allow-discrete, overlay 0.3s allow-discrete;
}

[popover]:popover-open::backdrop {
  opacity: 1;
}

@starting-style {
  [popover]:popover-open::backdrop {
    opacity: 0;
  }
}

5. Erreurs fréquentes

Erreur 1 : Utiliser les Container Queries sans container-type
/* FAUX — pas de conteneur déclaré */
.card-wrapper { /* rien */ }
@container (min-width: 400px) { /* ne fonctionne pas */ }

/* CORRECT — déclarer le conteneur */
.card-wrapper {
  container-type: inline-size;
}
@container (min-width: 400px) {
  .card { flex-direction: row; }
}
Erreur 2 : Confondre container-type: size et inline-size
/* ATTENTION — size crée un containment en HAUTEUR aussi */
.el {
  container-type: size; /* Peut casser la mise en page ! */
}

/* RECOMMANDÉ — la plupart du temps, seul inline-size est nécessaire */
.el {
  container-type: inline-size; /* Sécuritaire */
}
Erreur 3 : Utiliser @layer dans le mauvais ordre
/* FAUX — utilities en premier = écrasé */
@layer utilities, components, base;

/* CORRECT — du moins prioritaire au plus prioritaire */
@layer base, components, utilities;
Erreur 4 : Nesting trop profond
/* FAUX — 4+ niveaux de nesting, illisible */
.page {
  & .content {
    & .sidebar {
      & .widget {
        & .title { }
      }
    }
  }
}

/* CORRECT — max 2-3 niveaux */
.page {
  .sidebar {
    .widget-title { }
  }
}
Erreur 5 : Oublier allow-discrete dans les transitions de display
/* FAUX — display ne transitionne pas par défaut */
.tooltip {
  transition: opacity 0.3s, display 0.3s;
}

/* CORRECT — ajouter allow-discrete */
.tooltip {
  transition: opacity 0.3s, display 0.3s allow-discrete;
}
Erreur 6 : Utiliser left/right au lieu des Logical Properties
/* PAS PORTABLE — ne fonctionne pas en RTL */
.el { margin-left: 1rem; }

/* PORTABLE — fonctionne en LTR et RTL */
.el { margin-inline-start: 1rem; }
Erreur 7 : Ne pas définir color-scheme sur :root
/* FAUX — le navigateur ne sait pas qu'on supporte dark */
@media (prefers-color-scheme: dark) {
  body { background: #0f172a; }
  /* Les contrôles natifs restent en mode clair ! */
}

/* CORRECT */
:root { color-scheme: light dark; }
@media (prefers-color-scheme: dark) {
  body { background: #0f172a; }
  /* Les contrôles natifs basculent en mode sombre */
}

6. Bonnes pratiques

Règles d'or du CSS moderne :
  • Utiliser CSS Nesting natif — pas besoin de Sass pour le nesting simple.
  • Container Queries pour les composants — chaque composant doit s'adapter à son conteneur, pas à la fenêtre.
  • @layer pour l'architecture — contrôler la cascade explicitement.
  • :has() au lieu de JavaScript — pour les styles conditionnels basés sur les enfants.
  • Logical Properties par défaut — pour la compatibilité RTL native.
  • oklch pour les couleurs — luminosité perceptuellement uniforme.
  • color-scheme sur :root — pour le dark mode natif.
  • Popover API pour les overlays — pas besoin de JS pour les tooltips et popups simples.
  • @starting-style pour les animations d'entrée — propre et sans JS.

Quand utiliser chaque fonctionnalité

Besoin Fonctionnalité CSS moderne
Imbriquer des sélecteurs CSS Nesting natif
Contrôler la cascade entre libs @layer
Composant responsive Container Queries
Cibler un parent depuis un enfant :has()
Limiter la portée des styles @scope
Support RTL automatique Logical Properties
Carrousel / galerie Scroll Snap
Couleurs de formulaires accent-color
Dark mode natif color-scheme
Couleurs perceptuellement uniformes oklch / oklab
Mélanger des couleurs color-mix()
Animations d'entrée @starting-style
Overlays sans JS Popover API

7. Mini-défi

Défi 1 : Refactorer avec CSS Nesting

Prenez ce CSS classique et réécrivez-le avec le CSS Nesting natif :

/* AVANT */
.card { background: white; border-radius: 12px; }
.card__title { font-weight: 700; }
.card__text { color: #64748b; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card--featured { border-color: #2563eb; }

/* APRÈS (indices) */
.card {
  background: white;
  border-radius: 12px;
  &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
  &__title { font-weight: 700; }
  &__text { color: #64748b; }
  &--featured { border-color: #2563eb; }
}

Défi 2 : Créer un layout Container Query

Créez un composant carte qui s'adapte à son conteneur : vertical en dessous de 300px, horizontal au-dessus.

Défi 3 : @layer pour un design system

Organisez un design system en 3 couches : base (reset), components (card, btn), utilities (hidden, text-center). Testez que les utilities écrasent bien les components.

Défi 4 : :has() pour un formulaire

Créez un formulaire où le groupe d'inputs change d'apparence quand l'input est focusé, en utilisant uniquement :has().

Défi 5 : Scroll Snap carousel

Créez un carrousel horizontal avec Scroll Snap qui affiche 3 cartes visibles et s'arrête proprement sur chaque carte au scroll.

8. Projets pratiques

Projet 1 : Design system CSS moderne

Créez un design system utilisant uniquement les fonctionnalités CSS modernes :

  • Variables CSS avec oklch pour les couleurs
  • color-mix() pour dériver les nuances
  • CSS Nesting pour organiser les composants
  • @layer pour la cascade
  • Container Queries pour les composants responsive
  • Logical Properties pour le RTL-ready
  • accent-color pour les formulaires
  • color-scheme pour le dark mode

Projet 2 : Galerie avec Scroll Snap

Créez une galerie photo avec :

  • Scroll Snap horizontal et vertical
  • Navigation par clics (scroll-behavior: smooth)
  • Container Queries pour adapter les miniatures
  • Popover API pour les détails photo
  • @starting-style pour les animations d'entrée

Projet 3 : Dashboard responsive

Créez un dashboard utilisant :

  • Container Queries pour chaque widget
  • CSS Nesting pour l'organisation
  • :has() pour les états de formulaire
  • @layer pour séparer reset/components/utilities
  • oklch pour la palette de couleurs
  • Logical Properties pour la direction

9. Révision

Récapitulatif des fonctionnalités

Fonctionnalité Syntaxe Usage principal
CSS Nesting & { } Imbriquer les sélecteurs
@layer @layer base, components, utils; Contrôler la cascade
Container Queries container-type: inline-size; Responsive par composant
:has() .parent:has(.child) Sélecteur parent
@scope @scope (.a) to (.b) { } Portée de styles
Logical Properties margin-inline-start Support RTL
Scroll Snap scroll-snap-type: x mandatory; Carrousels magnétiques
accent-color accent-color: #2563eb; Couleurs formulaires natifs
color-scheme color-scheme: light dark; Dark mode natif
oklch() oklch(0.55 0.2 250) Couleurs perceptuellement uniformes
color-mix() color-mix(in oklch, #000 20%, #fff) Mélanger des couleurs
@starting-style @starting-style { .el { opacity: 0; } } Animations d'entrée
Popover API popovertarget="id" Overlays natifs sans JS

Quiz rapide

  1. Comment imbriquer un pseudo-élément avec CSS Nesting ?
  2. Quelle propriété déclare un conteneur pour les Container Queries ?
  3. Comment organiser les couches avec @layer ?
  4. Comment cibler un formulaire qui contient un input en erreur ?
  5. Quelle propriété logique remplace margin-left ?
  6. Quel type de scroll-snap est le plus strict ?
  7. Qu'est-ce que oklch et pourquoi l'utiliser ?
  8. Comment mélanger deux couleurs en CSS ?
  9. Quel attribut HTML active le Popover API ?
  10. À quoi sert @starting-style ?
Réponses :
  1. Avec &::before { } ou &::after { }.
  2. container-type: inline-size;
  3. Dans l'ordre croissant de priorité : @layer base, components, utilities;
  4. Avec :has() : .form:has(.input--error) { border-color: red; }
  5. margin-inline-start (gauche en LTR, droite en RTL).
  6. x mandatory — force l'alignement sur un point de snap.
  7. Un espace colorimétrique où la luminosité est perceptuellement uniforme, idéal pour créer des palettes harmonieuses.
  8. Avec color-mix(in oklch, couleur1 50%, couleur2 50%).
  9. popovertarget="id-de-l-element"
  10. Définir les styles initiaux d'un élément avant qu'il n'entre dans le DOM, pour les animations d'entrée.

Cheat sheet rapide

/* ============================================
   CSS MODERNE — Cheat Sheet
   ============================================ */

/* --- CSS NESTING --- */
.card {
  &:hover { }
  &__title { }
  &--featured { }
  @media (min-width: 768px) { }
}

/* --- @LAYER --- */
@layer reset, base, components, utilities;

/* --- CONTAINER QUERIES --- */
.wrap { container-type: inline-size; container-name: card; }
@container card (min-width: 400px) { .card { } }

/* --- :HAS() --- */
.form:has(input:focus) { }
.card:has(.image) { }
li:has(+ li:hover) { }

/* --- LOGICAL PROPERTIES --- */
margin-inline-start   /* gauche LTR / droite RTL */
margin-inline-end     /* droite LTR / gauche RTL */
margin-block-start    /* haut */
margin-block-end      /* bas */
padding-inline        /* left + right */
inset-inline          /* left + right (position) */
inline-size           /* width */
block-size            /* height */

/* --- SCROLL SNAP --- */
.container { scroll-snap-type: x mandatory; }
.item { scroll-snap-align: start; }

/* --- ACCENT-COLOR --- */
:root { accent-color: #2563eb; }

/* --- COLOR-SCHEME --- */
:root { color-scheme: light dark; }

/* --- OKLCH --- */
--blue: oklch(0.55 0.2 250);

/* --- COLOR-MIX --- */
--darker: color-mix(in oklch, var(--c) 80%, black);

/* --- @STARTING-STYLE --- */
.el { opacity: 1; transition: opacity 0.3s; }
@starting-style { .el { opacity: 0; } }

/* --- POPOVER --- */
<div popover>...</div>
<button popovertarget="id">Toggle</button>
[popover]:popover-open { opacity: 1; }
@starting-style { [popover]:popover-open { opacity: 0; } }