ASTROWP
← retour

Container queries : ce que je change dans ma façon de penser le responsive

Pendant des années, j'ai eu un réflexe automatique : un composant doit changer de mise en page ? Je sors une media query. @media (max-width: 768px), empiler la carte, réduire la police, masquer un bloc. Le geste est tellement ancré qu'il ne passe même plus par la réflexion.

Le problème, c'est que ce réflexe pose la mauvaise question. Une media query répond à « quelle taille fait l'écran ? ». Ce que je veux savoir, la plupart du temps, c'est « quelle taille fait l'espace disponible pour ce composant précis ? ». Ce sont deux questions différentes, et confondre les deux finit toujours par coûter cher.

Le cas qui révèle le problème

Prenons une carte produit ordinaire : image, titre, prix, bouton. Rien d'exotique. Le problème arrive quand cette carte doit vivre dans deux contextes : en pleine largeur dans une grille de résultats, et dans une barre latérale étroite (une section « produits similaires », par exemple).

Avec une media query, la carte se comporte de la même façon partout tant que le viewport ne change pas. Sur un écran large, elle s'affiche en pleine largeur dans la grille... et tout aussi large dans la sidebar de 280px, où elle déborde ou s'écrase, alors qu'il reste des centaines de pixels libres à côté sur le reste de la page. La media query ne peut pas le savoir : elle ne voit que la taille de l'écran, jamais celle du conteneur qui héberge réellement le composant.

La solution classique, c'était de dupliquer le composant (une variante « carte-sidebar », une variante « carte-grille ») ou de le truffer de props et de classes conditionnelles pour lui dire dans quel contexte il se trouve. Ça marche, mais ça revient à faire à la main ce que le CSS devrait faire tout seul.

Ce que changent les container queries

L'idée des container queries, c'est de déplacer la question : au lieu d'interroger l'écran, le composant interroge son propre conteneur. Pour ça, il faut d'abord désigner ce conteneur.

.carte-produit-conteneur {
  container-type: inline-size;
  container-name: carte;
}

container-type: inline-size dit au navigateur : surveille la largeur disponible de cet élément, et rends-la interrogeable. Une fois ce conteneur déclaré, la carte à l'intérieur peut réagir à sa propre largeur, indépendamment de la taille de l'écran :

.carte-produit {
  display: flex;
  flex-direction: column;
}

@container carte (min-width: 400px) {
  .carte-produit {
    flex-direction: row;
    align-items: center;
  }
}

Résultat : dans la grille pleine largeur, le conteneur dépasse 400px, la carte passe en ligne (image à gauche, texte à droite). Dans la sidebar étroite, le même composant, avec le même CSS, reste empilé. Aucune classe conditionnelle, aucune variante à maintenir en double. C'est la même carte qui s'adapte à l'endroit où on la pose.

Le vrai changement, ce n'est pas la syntaxe

La partie technique est simple, une fois qu'on l'a vue une fois. Ce qui est plus lent à changer, c'est le réflexe mental derrière.

Une media query pense en page : on découpe l'écran en tranches (mobile, tablette, desktop) et chaque composant hérite de la tranche dans laquelle il se trouve. Une container query pense en composant : chaque bloc porte sa propre logique de responsive, indépendamment de l'endroit où il atterrit.

C'est exactement ce qui manquait pour construire de vrais composants réutilisables. Sur un design system ou une bibliothèque de composants partagée entre plusieurs sites, ce distinguo change concrètement ce qu'on peut promettre : un composant conçu avec des container queries peut être livré une seule fois et déposé n'importe où (grille, sidebar, modale, colonne étroite d'un article) sans qu'il faille prévoir en amont tous les contextes possibles ni écrire une variante pour chacun.

Ce même distinguo se retrouve dans les unités de mesure. À côté des unités classiques (px, rem, vw), les container queries apportent leurs propres unités : cqw, cqh, cqi, cqb, cqmin, cqmax, relatives à la taille du conteneur plutôt qu'à celle du viewport. Sur la carte produit, ça permet de faire varier la taille du titre selon la largeur réellement disponible, sans passer par une nouvelle règle @container :

.carte-produit__titre {
  font-size: clamp(1rem, 4cqi, 1.5rem);
}

4cqi veut dire « 4 % de la largeur du conteneur, sur son axe inline ». Le titre grossit dans la grille pleine largeur, reste compact dans la sidebar, sans qu'on ait besoin d'écrire un second point de rupture juste pour la typographie.

Sur une bibliothèque de composants partagée entre plusieurs produits, un autre cas revient vite : plusieurs conteneurs imbriqués. Une carte peut elle-même contenir un bloc qui a besoin de connaître sa propre largeur, indépendamment de celle de la carte. container-name sert exactement à ça : @container carte (min-width: 400px) cible le conteneur nommé carte le plus proche dans l'arbre, même s'il existe un autre conteneur, avec un autre nom, plus loin entre les deux.

Les limites à connaître avant de tout réécrire

Deux points méritent d'être gardés en tête avant de basculer un projet entier.

Ensuite, un piège de performance : déclarer container-type haut dans l'arbre du DOM, sur un conteneur qui englobe beaucoup de contenu, force le navigateur à recalculer la mise en page de tout ce qui est en dessous à chaque changement de taille. Le bon réflexe, c'est de conteneuriser au plus près du composant qui a réellement besoin de s'adapter, pas au niveau d'un wrapper global « pour faire simple ».

Ce qu'il reste à ajuster

Les media queries ne disparaissent pas pour autant : elles restent la bonne réponse quand la question porte vraiment sur l'écran (adapter la navigation générale du site, gérer l'orientation, ajuster une mise en page globale). La container query prend le relais quand la question porte sur un composant précis et sur l'espace qu'on lui donne, pas sur l'écran qui l'entoure.

Le réflexe à corriger n'est donc pas « remplacer toutes les media queries », mais se poser la bonne question avant d'écrire la première ligne : est-ce que ce composant doit réagir à l'écran, ou à la place qu'on lui laisse ? Dans mon expérience, la deuxième réponse revient bien plus souvent qu'on ne le pense au moment d'écrire le CSS.

Sources