Pourquoi les CSS modernes changent fondamentalement le développement web
Pendant des années, le CSS a été limité : media queries basées sur la largeur du viewport, pas de vraie grille héritée, des hacks partout. En 2025, deux fonctionnalités révolutionnaires changent la donne : les Container Queries permettent aux composants de s’adapter à leur conteneur (pas au viewport), et le Subgrid permet aux enfants d’hériter de la grille de leur parent. Ensemble, ils rendent le CSS véritablement composant-first.
Container Queries : le responsive par composant
Le problème des media queries
Les media queries traditionnelles réagissent à la largeur du viewport. Mais un composant carte peut apparaître dans une sidebar de 300px OU dans un contenu principal de 800px sur le même écran. Avec les media queries, impossible de différencier ces deux contextes — le viewport est identique.
/* AVANT : media query = largeur du viewport */
@media (min-width: 768px) {
.card { display: flex; }
}
/* Casse dans la sidebar de 300px sur un écran de 1200px */
/* APRÈS : container query = largeur du conteneur parent */
@container (min-width: 400px) {
.card { display: flex; }
}
/* S'adapte à l'espace réel disponible */
Mise en place des container queries
/* 1. Déclarer un containment context */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Raccourci */
.card-wrapper {
container: card / inline-size;
}
/* 2. Écrire les container queries */
.card {
display: grid;
gap: 16px;
}
/* Quand le conteneur fait plus de 400px */
@container card (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
align-items: start;
}
}
/* Quand le conteneur fait plus de 700px */
@container card (min-width: 700px) {
.card {
grid-template-columns: 300px 1fr;
gap: 24px;
}
.card-title { font-size: 1.5rem; }
}
Le même composant .card s’adapte automatiquement qu’il soit dans une sidebar de 350px, un contenu de 600px, ou un layout pleine largeur — sans aucune media query viewport.
Container Query Units
Nouvelles unités relatives au conteneur :
/* cqw = 1% de la largeur du conteneur */
/* cqi = 1% de la taille inline du conteneur */
/* cqb = 1% de la taille block */
/* cqmin/cqmax = min/max de cqi et cqb */
.card-title {
font-size: clamp(1rem, 3cqi, 2rem);
}
.card-padding {
padding: 4cqi;
}
/* Typographie fluide relative au conteneur */
.hero-heading {
font-size: clamp(1.5rem, 5cqi + 1rem, 4rem);
}
Style Queries (expérimental)
/* Le parent définit un thème via custom property */
.section-dark { --theme: dark; background: #1a1a1a; }
.section-light { --theme: light; background: #fff; }
/* Les enfants s'adaptent automatiquement */
@container style(--theme: dark) {
.card {
background: #2a2a2a;
color: #e0e0e0;
}
}
@container style(--theme: light) {
.card {
background: #fff;
color: #333;
}
}
/* Support : Chrome 111+, Firefox/Safari en cours */
Subgrid : l’alignement parfait hérité
Le problème d’alignement
CSS Grid crée des layouts puissants. Mais les petits-enfants de la grille ne connaissent pas les lignes du grand-parent. Résultat : dans une grille de cartes, les titres, descriptions et prix ne s’alignent pas horizontalement entre les cartes quand les textes ont des longueurs différentes.
La solution subgrid
/* Grille de produits avec alignement parfait */
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: auto auto 1fr auto;
gap: 24px;
}
.product-card {
display: grid;
grid-row: span 4;
grid-template-rows: subgrid; /* Hérite des lignes du parent ! */
gap: 8px;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
/* Tous les titres alignés sur la même ligne,
toutes les descriptions au même niveau,
tous les prix en bas — même avec des textes
de longueurs différentes */
.product-image { grid-row: 1; aspect-ratio: 16/10; object-fit: cover; }
.product-title { grid-row: 2; padding: 16px 16px 0; font-weight: 600; }
.product-desc { grid-row: 3; padding: 8px 16px; color: #666; }
.product-price { grid-row: 4; padding: 0 16px 16px; font-weight: 700; }
Subgrid pour les colonnes
/* Formulaire avec labels et inputs parfaitement alignés */
.form {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 16px 12px;
}
.form-row {
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
}
.form-row label { grid-column: 1; }
.form-row input { grid-column: 2; }
.form-row button { grid-column: 3; }
/* Tous les labels = même largeur (auto = la plus large),
tous les inputs s'étendent identiquement */
Support : Tous les navigateurs majeurs depuis fin 2023 (Chrome 117, Firefox 71, Safari 16). Utilisable en production.
Autres fonctionnalités CSS modernes essentielles
:has() — Le sélecteur parent
/* Styliser un parent selon ses enfants — enfin possible ! */
/* Formulaire avec champ en erreur */
.form-group:has(.input--error) {
border-left: 3px solid red;
background: #fff0f0;
}
/* Carte avec ou sans image */
.card:has(img) { grid-template-rows: 200px auto 1fr; }
.card:not(:has(img)) { grid-template-rows: auto 1fr; }
/* Layout conditionnel */
body:has(.sidebar) .main { max-width: 800px; }
body:not(:has(.sidebar)) .main { max-width: 1200px; }
/* Navigation avec sous-menu ouvert */
.nav-item:has(.submenu:hover) { background: #f0f0f0; }
Nesting natif — Fini le Sass pour ça
/* CSS Nesting natif — tous les navigateurs 2024+ */
.card {
background: white;
border-radius: 12px;
padding: 16px;
& .card-title {
font-size: 1.25rem;
font-weight: 600;
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
& .card-title { color: var(--color-primary); }
}
/* Media queries imbriquées */
@media (width >= 768px) {
display: flex;
gap: 24px;
}
/* Container queries imbriquées */
@container (width >= 400px) {
grid-template-columns: 150px 1fr;
}
}
Scroll-driven Animations — Animations sans JavaScript
/* Barre de progression de lecture — 0 JavaScript */
.reading-progress {
position: fixed;
top: 0; left: 0;
width: 100%;
height: 3px;
background: var(--color-primary);
transform-origin: left;
animation: grow linear;
animation-timeline: scroll();
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Fade-in au scroll */
.section {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
Anchor Positioning — Tooltips en CSS pur
/* Ancre */
.button { anchor-name: --my-btn; }
/* Tooltip positionné par rapport au bouton */
.tooltip {
position: fixed;
position-anchor: --my-btn;
top: anchor(bottom);
left: anchor(center);
translate: -50% 8px;
position-try-fallbacks: flip-block;
}
/* Support Chrome 125+ — progressive enhancement */
Architecture CSS moderne recommandée
/* 1. Tokens via custom properties */
:root {
--color-primary: #1a56db;
--color-text: #1a1a1a;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 32px;
--radius-md: 8px;
}
/* 2. Reset minimal */
*, *::before, *::after { box-sizing: border-box; margin: 0; }
/* 3. Composants avec container queries + nesting */
.card-container { container: card / inline-size; }
.card {
display: grid;
gap: var(--spacing-sm);
padding: var(--spacing-md);
@container card (width >= 400px) {
grid-template-columns: 200px 1fr;
}
@container card (width >= 700px) {
grid-template-columns: 300px 1fr;
padding: var(--spacing-lg);
}
}
/* 4. Grilles avec subgrid pour l'alignement */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg);
}
Les container queries et subgrid résolvent des problèmes que les développeurs contournaient avec du JavaScript depuis des années. Avec un support navigateur désormais universel, adoptez-les maintenant. Commencez par convertir vos media queries de composants en container queries, puis utilisez subgrid pour vos grilles de cartes avec contenu variable.