Chapitre 34 : Gestion avancée des thèmes avec les Container Queries
Adaptation du style selon le contexte du parent via @container
Pendant des années, le responsive design a reposé quasi exclusivement sur les Media Queries. Cependant, les Media Queries ont une limite fondamentale : elles ne regardent que la taille de la fenêtre du navigateur (le viewport). Dans une architecture moderne basée sur des composants (React, Vue, Web Components), un composant peut être placé dans une barre latérale étroite ou dans une zone de contenu large.
Le composant doit être capable de s'adapter non pas à l'écran, mais à l'espace qui lui est alloué. C'est ici qu'interviennent les Container Queries.
Le Concept de Container Queries
1. Quoi
Les Container Queries sont une fonctionnalité CSS qui permet d'appliquer des styles à un élément en fonction de la taille ou des caractéristiques de son conteneur parent, plutôt que de la taille globale de la fenêtre.
Pour qu'un élément puisse être "interrogé" par une @container query, un ancêtre doit d'abord être défini comme un conteneur via la propriété container-type.
2. Pourquoi
Dans un système de design professionnel, on crée des composants "atomiques" (boutons, cartes, widgets). Si vous utilisez des Media Queries pour styliser une Card, vous forcez cette carte à avoir le même aspect partout sur la page dès que l'écran atteint 768px.
Or, si cette même Card est placée dans une colonne de 300px (même sur un écran de 1920px), elle aura l'air compressée ou mal formatée. Les Container Queries permettent au composant d'être véritablement autonome : il "sent" l'espace disponible et change son thème ou sa disposition en conséquence.
3. Comment
A. Syntaxe de base
L'implémentation se déroule en deux étapes : la définition du conteneur et la requête.
/* 1. On définit l'élément parent comme conteneur */
.card-container {
container-type: inline-size;
/* inline-size surveille la largeur (axe horizontal) */
container-name: card-wrapper; /* Optionnel : permet de cibler un conteneur spécifique */
}
/* 2. On applique des styles à l'enfant selon la taille du parent */
@container card-wrapper (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
}
B. Cas concret : Thème adaptatif de Widget
Imaginons un widget de profil utilisateur qui doit passer d'un mode "Compact" (vertical) à un mode "Détaillé" (horizontal) selon l'endroit où il est inséré dans le dashboard.
:root {
--bg-compact: #f4f4f4;
--bg-detailed: #ffffff;
--text-main: #333;
--accent-color: #007bff;
}
/* Le conteneur qui peut varier selon le layout du dashboard */
.widget-slot {
container-type: inline-size;
container-name: profile-slot;
width: 100%;
}
/* Style par défaut (Mode Compact - Mobile first / Small space) */
.profile-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
background-color: var(--bg-compact);
border-radius: 8px;
text-align: center;
transition: all 0.3s ease;
}
.profile-card img {
width: 60px;
height: 60px;
border-radius: 50%;
}
/* Mode Détaillé : Activé quand le parent fait plus de 500px */
@container profile-slot (min-width: 500px) {
.profile-card {
flex-direction: row;
text-align: left;
background-color: var(--bg-detailed);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
gap: 1.5rem;
}
.profile-card img {
width: 100px;
height: 100px;
}
.profile-info {
display: flex;
flex-direction: column;
justify-content: center;
}
}
C. Limitations
- L'élément ne peut pas s'interroger lui-même : Un élément ne peut pas être son propre conteneur pour une
@containerquery. La requête s'applique toujours aux enfants du conteneur. - Performance : Bien que très optimisées, un abus de conteneurs imbriqués peut augmenter la charge de calcul du navigateur lors du redimensionnement.
container-type: size: Contrairement àinline-size(largeur),sizesurveille aussi la hauteur. Cela nécessite souvent que le parent ait une hauteur fixe, sinon cela peut créer des boucles de rendu infinies (le contenu change la taille du parent, qui change le style du contenu, qui change la taille du parent...).
4. Zone de Danger
❌ Erreur commune : Confondre @media et @container.
- Mauvaise pratique : Utiliser
@media (min-width: 768px)pour changer la disposition d'un composant qui est situé dans une sidebar fixe de 300px. Le composant passera en mode "large" alors qu'il n'a pas de place. - Bonne pratique : Utiliser
@container (min-width: 768px)sur le parent du composant. Le composant ne passera en mode "large" que si l'espace réel disponible dans la sidebar devient suffisant.
❌ Erreur commune : Oublier le container-type.
- Si vous écrivez
@container (min-width: 300px)sans avoir définicontainer-typesur un ancêtre, la règle sera simplement ignorée par le navigateur.
Flux de fonctionnement des Container Queries
Questions clés
1. Quelle est la différence fondamentale entre container-type: inline-size et container-type: size ?
Découvrir la réponse
inline-size surveille uniquement l'axe horizontal (la largeur dans les langues occidentales). C'est le choix le plus courant car il ne perturbe pas le flux naturel de la hauteur du document. size surveille à la fois la largeur et la hauteur, mais impose souvent de définir une hauteur explicite sur le conteneur pour éviter les problèmes de rendu.
2. Peut-on nommer plusieurs conteneurs et les cibler spécifiquement ?
Découvrir la réponse
Oui, via la propriété container-name. Cela permet d'éviter que des styles ne s'appliquent accidentellement si un composant est imbriqué dans plusieurs conteneurs ayant des tailles similaires. On utilise alors @container "nom-du-conteneur" (min-width: ...).
3. Les Container Queries remplacent-elles les Media Queries ?
Découvrir la réponse
Non. Elles sont complémentaires. Les Media Queries restent indispensables pour les décisions globales (ex: masquer le menu principal sur mobile, changer la police globale du site). Les Container Queries sont dédiées à la modularité des composants.
4. Comment gérer la compatibilité avec les anciens navigateurs ?
Découvrir la réponse
On utilise généralement une stratégie de "Progressive Enhancement". On définit des styles de base (souvent le mode compact) et on ajoute les Container Queries. Pour les navigateurs ne supportant pas la fonctionnalité, on peut utiliser @supports (container-type: inline-size) pour isoler le code moderne.
Mise en pratique
Exercice 1 : Reproduction guidée
Créez un composant "Alerte" simple.
- Le parent
.alert-wrapperdoit être un conteneur. - Par défaut (moins de 400px), l'alerte affiche l'icône et le texte l'un au-dessus de l'autre (centrés).
- Au-dessus de 400px, l'alerte doit passer en
flex-direction: rowavec l'icône à gauche et le texte à droite.
Découvrir la solution commentée
/* Définition du conteneur */
.alert-wrapper {
container-type: inline-size;
width: 100%;
}
/* Style de base (Compact) */
.alert {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 1rem;
background: #fff3cd;
border: 1px solid #ffeeba;
}
/* Adaptation selon le conteneur */
@container (min-width: 400px) {
.alert {
flex-direction: row;
text-align: left;
}
}
Exercice 2 : Adaptation (Thématisation)
Reprenez l'exercice précédent. Ajoutez une condition supplémentaire : si le conteneur dépasse 700px, changez la couleur de fond de l'alerte en bleu (#cce5ff) et augmentez la taille de la police du texte.
Découvrir la solution commentée
.alert-wrapper {
container-type: inline-size;
width: 100%;
}
.alert {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 1rem;
background: #fff3cd;
border: 1px solid #ffeeba;
transition: background 0.3s ease;
}
@container (min-width: 400px) {
.alert {
flex-direction: row;
}
}
/* Nouvelle règle pour le thème "Large" */
@container (min-width: 700px) {
.alert {
background: #cce5ff;
border-color: #b8daff;
font-size: 1.2rem;
}
}
Exercice 3 : Conception (Problème métier)
Vous développez un système de "Cartes de Produit" pour un e-commerce.
- Dans la grille principale, les cartes sont larges et affichent : Image, Titre, Prix, Description et Bouton "Ajouter".
- Dans une barre latérale "Produits suggérés", les cartes sont étroites et ne doivent afficher que : Image (petite), Titre et Prix. La description et le bouton doivent disparaître.
HTML de départ :
<!-- Contexte 1 : Grille principale (large) -->
<div class="product-slot" style="width: 400px;">
<div class="product-card">
<img class="product-image" src="https://placehold.co/350x200" alt="Casque Bluetooth">
<h3 class="product-title">Casque Bluetooth Pro</h3>
<p class="product-price">129,99 €</p>
<p class="product-description">Son exceptionnel avec réduction de bruit active et 40h d'autonomie.</p>
<button class="product-button">Ajouter au panier</button>
</div>
</div>
<!-- Contexte 2 : Barre latérale (étroit) — même HTML, comportement différent -->
<div class="product-slot" style="width: 180px;">
<div class="product-card">
<img class="product-image" src="https://placehold.co/180x180" alt="Casque Bluetooth">
<h3 class="product-title">Casque Bluetooth Pro</h3>
<p class="product-price">129,99 €</p>
<p class="product-description">Son exceptionnel avec réduction de bruit active et 40h d'autonomie.</p>
<button class="product-button">Ajouter au panier</button>
</div>
</div>
Le principe des Container Queries est que le HTML est identique dans les deux contextes. C'est uniquement la taille du .product-slot qui change. La carte se recompose automatiquement sans modifier une seule ligne de HTML.
Implémentez cela en utilisant les Container Queries pour que la carte s'adapte automatiquement peu importe où elle est placée.
Découvrir la solution commentée
/* Le slot où la carte est insérée */
.product-slot {
container-type: inline-size;
}
/* Structure de la carte */
.product-card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
border: 1px solid #ddd;
}
.product-image {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
/* On cache les éléments détaillés par défaut (approche mobile-first / small-first) */
.product-description,
.product-button {
display: none;
}
/* Mode "Large" : On affiche tout et on organise le layout */
@container (min-width: 350px) {
.product-description,
.product-button {
display: block;
}
.product-card {
/* Optionnel : on pourrait passer en grid ici */
grid-template-columns: 150px 1fr;
display: grid;
}
}
/* Mode "Mini" : Ajustement spécifique pour les très petits espaces */
@container (max-width: 200px) {
.product-title {
font-size: 0.8rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}