Pendant des années, le responsive design a reposé sur une seule mesure : la largeur de la fenêtre. Mais un composant ne vit pas dans la fenêtre, il vit dans son conteneur — une carte placée dans une barre latérale étroite n’a pas la même place que la même carte au centre d’une page large. Le CSS moderne a comblé ce fossé avec les container queries, le sélecteur :has() et le nesting natif, et Tailwind v4 les expose tous comme des utilitaires. À la fin de ce tutoriel, les composants de StockLab s’adapteront à l’espace réel dont ils disposent, réagiront à leur propre contenu, et vous saurez écrire du CSS imbriqué propre quand l’utilitaire ne suffit pas.
🎯 Ce que vous allez apprendre
- Adapter un composant à la taille de son conteneur avec les container queries (
@container,@sm:,@max-md:). - Réagir au contenu d’un élément avec la variante
has-*fondée sur le sélecteur CSS:has(). - Styler conditionnellement avec les variantes composables
not-*,group-*etpeer-*. - Écrire du CSS imbriqué proprement, géré nativement par Tailwind via Lightning CSS.
🛠️ Ce que vous allez construire
Nous rendons la fiche produit de StockLab vraiment intelligente : elle passera en disposition horizontale quand son conteneur est large, peu importe la taille de l’écran ; elle se mettra en évidence quand elle contient un badge « promo » ; et un champ de recherche signalera son état actif à son entourage. Le tout avec les fonctionnalités CSS les plus récentes.
Prérequis
- Un projet Tailwind v4 et l’interface des leçons précédentes.
- Une bonne maîtrise du responsive classique avec les points de rupture.
- Test express : si la différence entre « taille de l’écran » et « taille du conteneur » vous parle, vous êtes prêt.
- ⏱️ Temps estimé : environ 35 minutes.
Pourquoi les points de rupture classiques ne suffisent plus
Imaginez la fiche produit de StockLab réutilisée à deux endroits : dans la grille principale, large, et dans une barre latérale étroite de suggestions. Avec les points de rupture d’écran, la fiche se comporte identiquement aux deux endroits sur un même écran — alors qu’elle dispose d’espaces radicalement différents. C’est l’angle mort historique du responsive : md: mesure la fenêtre, pas le contexte réel du composant. Résultat, on finit par écrire des variantes spécifiques à chaque emplacement, ce qui tue la réutilisabilité. Les container queries résolvent exactement ce problème en mesurant le conteneur du composant plutôt que la fenêtre.
Étape 1 — Adapter un composant à son conteneur
On déclare un conteneur de requête avec la classe @container sur l’élément parent, puis on style les enfants avec des variantes préfixées par @ qui mesurent ce conteneur, et non l’écran. Rendons la fiche horizontale quand son conteneur dépasse une certaine largeur.
<div class="@container">
<article class="flex flex-col gap-4 @md:flex-row @md:items-center">
<img src="/visseuse.jpg" alt="Visseuse" class="rounded-md @md:w-40">
<div>
<h3 class="font-semibold">Visseuse sans fil</h3>
<p class="text-sm text-gray-500">37 en stock</p>
</div>
</article>
</div>
La logique ressemble au responsive classique, mais le repère a changé. @md:flex-row bascule la fiche en disposition horizontale dès que le conteneur atteint 28 rem (448 pixels), quelle que soit la taille de l’écran. Placez cette même fiche dans une colonne large : elle s’étale horizontalement. Placez-la dans une barre latérale étroite : elle reste empilée. Un seul composant, deux comportements corrects, zéro variante spécifique à l’emplacement. C’est un changement profond dans la manière de concevoir des composants réutilisables.
✅ Point d’étape — En plaçant la fiche dans des conteneurs de largeurs différentes, sa disposition change selon la place disponible, indépendamment de la taille de la fenêtre. Si rien ne se passe, vérifiez que la classe
@containerest bien sur le parent direct.
Étape 2 — Les tailles de conteneur et les conteneurs nommés
Comme pour les écrans, Tailwind fournit une échelle de tailles de conteneur, de @3xs (16 rem) à @7xl (80 rem), et la variante inverse @max-* pour cibler les conteneurs en dessous d’un seuil. Vous pouvez aussi nommer un conteneur quand plusieurs sont imbriqués, pour viser explicitement le bon.
<div class="@container/carte">
<article class="grid grid-cols-1 @sm/carte:grid-cols-2">
<!-- contenu -->
</article>
</div>
En écrivant @container/carte, on baptise ce conteneur « carte » ; la variante @sm/carte:grid-cols-2 mesure alors précisément ce conteneur-là, même s’il est lui-même niché dans un autre conteneur de requête. Cette précision évite les ambiguïtés dans les interfaces complexes où les conteneurs s’emboîtent, et c’est exactement le genre de situation qu’on rencontre dans un vrai tableau de bord.
Étape 3 — Réagir au contenu avec :has()
Le sélecteur :has() est l’une des avancées CSS les plus attendues : il permet à un parent de se styler selon ce qu’il contient — ce qui était impossible auparavant sans JavaScript. Tailwind l’expose via la variante has-*. Mettons en évidence toute fiche qui contient un badge de promotion.
<article class="rounded-lg bg-white p-4 has-[.badge-promo]:ring-2 has-[.badge-promo]:ring-amber-400">
<span class="badge-promo …">Promo</span>
<h3>Visseuse sans fil</h3>
</article>
La variante has-[.badge-promo]:ring-2 dit : « si cette fiche contient un élément de classe badge-promo, ajoute-lui un anneau de mise en évidence ». Retirez le badge, l’anneau disparaît tout seul. Aucune ligne de JavaScript, aucune classe supplémentaire à poser conditionnellement : le style suit l’existence réelle du contenu. C’est un changement de paradigme ; beaucoup de logique d’interface autrefois pilotée par script devient purement déclarative.
✅ Point d’étape — Les fiches contenant un badge promo affichent un anneau ambre ; les autres restent neutres. Si l’anneau n’apparaît pas, vérifiez la syntaxe entre crochets et que le sélecteur cible bien la classe du badge.
Étape 4 — Les variantes composables : not, group et peer
Tailwind v4 généralise des variantes qui se composent entre elles pour exprimer des conditions fines. not-* applique un style quand une condition n’est pas remplie ; group-* style un enfant selon l’état d’un ancêtre marqué ; peer-* style un élément selon l’état d’un frère. Illustrons avec un champ de recherche et son étiquette.
<input type="text" class="peer …" placeholder="Rechercher un article">
<p class="hidden peer-focus:block text-sm text-indigo-600">
Tapez pour filtrer le stock
</p>
On marque le champ avec peer ; l’aide en dessous est masquée par défaut (hidden) et se révèle avec peer-focus:block quand le champ voisin reçoit le focus. group-* fonctionne de la même manière mais entre un parent marqué group et ses descendants : survoler une carte entière peut ainsi révéler un bouton d’action niché à l’intérieur, avec group-hover:opacity-100. Quant à not-*, il cible l’absence d’un état : not-hover:opacity-70 atténue un élément tant qu’il n’est pas survolé. Ces variantes se combinent à l’envi et couvrent l’essentiel des interactions qui demandaient autrefois du JavaScript.
Étape 5 — Le nesting natif, sans préprocesseur
Quand vous écrivez du CSS personnalisé — pour styler un contenu riche venu d’un éditeur, par exemple — vous appréciez d’imbriquer les règles comme en Sass. Bonne nouvelle : Tailwind v4 gère le nesting nativement, grâce à Lightning CSS intégré au moteur. Vous n’avez besoin d’aucun préprocesseur ni plugin.
.article-riche {
color: var(--color-content);
h2 {
font-weight: 600;
margin-block: 1rem;
}
a:hover {
color: var(--color-accent);
}
}
Cette syntaxe imbriquée est compilée en CSS plat par Lightning CSS, qui ajoute aussi les préfixes vendeurs nécessaires et regroupe vos imports. C’est la même technologie qui, sous le capot, traite votre @import "tailwindcss";. Le bénéfice pratique : vous écrivez du CSS moderne et lisible pour les rares cas où l’utilitaire ne convient pas, sans alourdir votre chaîne d’outils d’un préprocesseur supplémentaire.
Un mot sur le support des navigateurs
Toutes ces fonctionnalités sont modernes, et c’est précisément pourquoi Tailwind v4 cible des navigateurs récents : Safari 16.4, Chrome 111 et Firefox 128 ou supérieurs. Les container queries, :has() et le nesting y sont pleinement disponibles. Si votre public utilise des navigateurs plus anciens, ces techniques ne s’appliqueront pas ; dans ce cas, prévoyez une dégradation gracieuse — la fiche reste empilée et lisible même sans container query — ou restez sur la branche 3.4 de Tailwind. Pour la grande majorité des projets actuels, ces fondations modernes sont un atout, pas un risque.
La fin d’un compromis vieux de quinze ans
Pour mesurer l’importance des container queries, il faut se rappeler la frustration qu’elles dissipent. Pendant près de quinze ans, le responsive design a reposé entièrement sur les requêtes média, qui ne savent mesurer qu’une chose : la fenêtre. Cela obligeait à un compromis permanent. Un composant censé être réutilisable — une fiche, une vignette, un encart — devait deviner dans quel contexte il serait posé, ou bien on multipliait les variantes spécifiques à chaque emplacement. Les systèmes de composants modernes, où l’on assemble des briques réutilisables, butaient sur cette limite : la même brique se comportait mal selon qu’on la plaçait dans une colonne large ou une barre latérale étroite, sur un écran pourtant identique.
Les container queries referment ce chapitre. En déplaçant la mesure de la fenêtre vers le conteneur, elles rendent enfin un composant vraiment autonome : il décide de sa disposition d’après l’espace qu’on lui accorde, pas d’après une propriété globale de la page. Pour une interface comme StockLab, faite de fiches réutilisées à plusieurs endroits, c’est un gain direct de robustesse : une seule définition, qui se comporte correctement partout. C’est aussi un changement de méthode : on conçoit désormais les composants en pensant à leurs paliers de conteneur, et l’on réserve les requêtes média à la mise en page d’ensemble de la page. Cette répartition — conteneur pour les composants, écran pour la structure globale — est en train de devenir la nouvelle norme du développement d’interfaces.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
Les variantes @md: ne font rien |
La classe @container manque sur le parent |
Ajouter @container sur le conteneur direct des éléments stylés |
Confusion entre md: et @md: |
md: mesure l’écran, @md: mesure le conteneur |
Choisir selon le repère voulu : fenêtre ou conteneur |
La variante has-[…] ne réagit pas |
Sélecteur mal formé ou cible inexistante | Vérifier la syntaxe entre crochets et l’existence de l’élément ciblé |
peer-focus: sans effet |
L’élément déclencheur ne porte pas la classe peer |
Marquer le champ avec peer et placer la cible après lui |
✅ Récapitulatif
Vous maniez désormais le CSS moderne tel que Tailwind v4 l’expose. Vous savez adapter un composant à son conteneur avec les container queries — un vrai bond pour la réutilisabilité —, réagir au contenu d’un élément avec has-*, exprimer des conditions fines avec not-*, group-* et peer-*, et écrire du CSS imbriqué propre sans préprocesseur. Les composants de StockLab ne dépendent plus seulement de la taille de l’écran : ils réagissent à leur espace réel et à leur propre contenu, ce qui est la marque d’une interface véritablement moderne.
🧾 Aide-mémoire
| Utilitaire | Effet |
|---|---|
@container + @md:flex-row |
Adapter selon la taille du conteneur |
@container/nom + @sm/nom: |
Cibler un conteneur nommé précis |
has-[.classe]:ring-2 |
Styler un parent selon son contenu |
peer + peer-focus:block |
Styler selon l’état d’un frère |
group + group-hover:opacity-100 |
Styler un enfant selon l’état du parent |
| Nesting CSS natif | Imbriquer les règles sans préprocesseur (Lightning CSS) |
💪 À vous de jouer
Faites en sorte qu’une carte de produit dont le stock est à zéro (elle contient un élément .rupture) soit grisée et non cliquable, en utilisant has-*. Bonus : révélez un bouton « Réapprovisionner » au survol de toute la carte avec group.
Voir une solution
<article class="group rounded-lg bg-white p-4 has-[.rupture]:opacity-50 has-[.rupture]:pointer-events-none">
<span class="rupture text-red-600 text-xs">Rupture</span>
<h3>Visseuse</h3>
<button class="opacity-0 group-hover:opacity-100 transition">Réapprovisionner</button>
</article>
has-[.rupture]:opacity-50 grise la carte dès qu’elle contient l’élément de rupture ; group-hover:opacity-100 fait apparaître le bouton quand on survole la carte entière.
Tutoriels frères
- Tailwind CSS moderne : le parcours et la carte des leçons — concepts, méthode et vue d’ensemble.
- Flexbox et Grid en utilities — les bases de mise en page que les container queries viennent enrichir.
- Tailwind utility-first et responsive — le responsive d’écran, à comparer au responsive de conteneur.
Ressources et références
- Documentation officielle — container queries
- Documentation officielle — has, group, peer et not
- Documentation officielle — compatibilité navigateurs
FAQ
Q : Quelle différence entre md: et @md: ?
R : md: applique un style à partir d’une largeur d’écran ; @md: l’applique à partir d’une largeur de conteneur. Le second rend un composant réutilisable dans des emplacements de tailles différentes.
Q : Le sélecteur :has() est-il sûr à utiliser aujourd’hui ?
R : Oui sur les navigateurs visés par Tailwind v4 (Safari 16.4+, Chrome 111+, Firefox 128+), où il est pleinement supporté. Prévoyez une dégradation gracieuse pour les très vieux navigateurs.
Q : Faut-il un plugin pour les container queries en v4 ?
R : Non. Elles sont intégrées au cœur de Tailwind v4 ; le plugin séparé de la v3 n’est plus nécessaire.
Q : Ai-je encore besoin de Sass pour le nesting ?
R : Non. Tailwind v4 traite le nesting nativement via Lightning CSS, qui gère aussi les préfixes vendeurs et le regroupement des imports.