Pourquoi les media queries ne suffisent plus toujours

Pendant des années, le responsive design a surtout reposé sur les media queries. On écrivait une règle pour les petits écrans, une autre pour les tablettes, puis une troisième pour les grands écrans. Cette approche fonctionne encore très bien pour structurer une page entière, mais elle devient vite limitée dès que l’on raisonne en composants.

Prenons une carte produit. Elle peut apparaître dans une grille à quatre colonnes sur une page d’accueil, dans une colonne latérale plus étroite, ou dans une liste de résultats. Avec des media queries classiques, la carte ne connaît que la largeur du viewport. Elle ignore l’espace réellement disponible dans son parent.

C’est précisément le problème que résolvent les container queries. Elles permettent à un composant CSS de s’adapter à la taille de son conteneur, et non plus seulement à la taille de la fenêtre.

Cette logique s’inscrit très bien dans une architecture frontend moderne, où l’on cherche à produire des composants robustes, réutilisables et moins dépendants de leur emplacement dans l’application. C’est le même type de réflexion que l’on retrouve lorsqu’on sécurise les frontières d’un système, par exemple avec le typage des données API en TypeScript : l’objectif est de rendre le code plus fiable en explicitant son contexte.

Le principe des container queries

Une container query fonctionne en deux étapes :

  1. on déclare qu’un élément est un conteneur observable ;
  2. on écrit des règles CSS qui s’appliquent selon la taille de ce conteneur.

Voici un exemple minimal :

.card-wrapper {
  container-type: inline-size;
}

@container (min-width: 480px) {
  .card {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 1rem;
  }
}

Ici, .card-wrapper devient un conteneur dont la largeur peut être interrogée. Quand ce conteneur atteint au moins 480px, la carte passe en mise en page à deux colonnes.

La valeur [inline-size](/articles/css-logical-properties) indique que l’on observe la taille dans l’axe inline, c’est-à-dire généralement la largeur dans une interface en français ou en anglais. C’est le choix le plus courant pour les composants responsives.

Un exemple concret : une carte de cours

Imaginons une carte utilisée pour présenter un cours de développement web. Elle doit rester lisible dans une colonne étroite, mais exploiter l’espace disponible lorsqu’elle est placée dans une zone plus large.

<div class="course-card-container">
  <article class="course-card">
    <img src="/images/react-course.jpg" alt="Interface de développement React" />
    <div class="course-card__content">
      <p class="course-card__tag">React</p>
      <h2>Créer des interfaces maintenables avec React</h2>
      <p>
        Apprenez à structurer vos composants, gérer les états locaux et
        concevoir une interface claire pour vos utilisateurs.
      </p>
      <a href="/formations/react">Voir la formation</a>
    </div>
  </article>
</div>

Le CSS de base peut rester mobile-first :

.course-card-container {
  container-type: inline-size;
}

.course-card {
  display: flex;
  flex-direction: column;
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
  border-radius: 1rem;
  overflow: hidden;
}

.course-card img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.course-card__content {
  padding: 1rem;
}

.course-card__tag {
  font-size: 0.875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

Puis on enrichit progressivement la mise en page selon la largeur du conteneur :

@container (min-width: 520px) {
  .course-card {
    display: grid;
    grid-template-columns: minmax(180px, 35%) 1fr;
  }

  .course-card img {
    height: 100%;
    aspect-ratio: auto;
  }

  .course-card__content {
    padding: 1.5rem;
  }
}

@container (min-width: 760px) {
  .course-card__content {
    display: grid;
    gap: 0.75rem;
  }

  .course-card h2 {
    font-size: clamp(1.5rem, 4cqi, 2.25rem);
  }
}

Le composant devient autonome. Il n’a plus besoin de savoir s’il est dans une sidebar, une grille ou une page dédiée. Il s’adapte à la place dont il dispose réellement.

Les unités relatives au conteneur

Les container queries s’accompagnent d’unités très utiles : cqw, cqh, cqi, cqb, cqmin et cqmax.

L’unité cqi, par exemple, représente 1 % de la taille inline du conteneur. Elle permet d’écrire des tailles fluides qui dépendent du composant lui-même.

.course-card h2 {
  font-size: clamp(1.25rem, 5cqi, 2rem);
}

Cette règle signifie que le titre reste entre 1.25rem et 2rem, avec une valeur fluide calculée selon la largeur du conteneur. C’est souvent plus pertinent qu’une valeur fondée sur vw, qui dépend de la largeur totale de la fenêtre.

Attention toutefois : ces unités doivent être utilisées avec mesure. Une interface trop fluide peut devenir difficile à contrôler, en particulier pour les textes longs, les formulaires ou les composants fortement contraints par l’accessibilité.

Nommer ses conteneurs pour éviter les ambiguïtés

Dans une interface complexe, un composant peut être imbriqué dans plusieurs conteneurs. Il est alors possible de nommer un conteneur pour cibler explicitement celui que l’on souhaite interroger.

.dashboard-panel {
  container-type: inline-size;
  container-name: panel;
}

@container panel (min-width: 640px) {
  .stats-card {
    grid-template-columns: repeat(3, 1fr);
  }
}

Cette pratique rend le code plus explicite. Elle évite qu’une règle dépende accidentellement du premier conteneur disponible dans l’arbre DOM.

On peut aussi utiliser la propriété raccourcie :

.dashboard-panel {
  container: panel / inline-size;
}

Cette syntaxe est compacte, mais elle peut être moins lisible pour des débutants. Dans un contexte pédagogique ou dans une équipe hétérogène, je recommande souvent de commencer avec container-type et container-name, puis de passer à la version raccourcie lorsque l’équipe est à l’aise.

Quand utiliser les container queries

Les container queries sont particulièrement pertinentes pour les composants réutilisables : cartes, widgets de tableau de bord, blocs éditoriaux, formulaires, encarts marketing, listes de résultats, composants e-commerce ou modules de navigation.

Elles sont moins adaptées aux grandes décisions structurelles globales. Pour modifier l’architecture générale d’une page selon la largeur de l’écran, les media queries restent souvent plus simples et plus lisibles.

Une bonne règle pratique consiste à séparer les responsabilités :

  • les media queries pilotent la structure globale de la page ;
  • les container queries pilotent le comportement interne des composants.

Cette séparation améliore la maintenabilité. Elle permet aussi de déplacer un composant sans devoir réécrire toute sa logique responsive.

Un piège courant : déclarer le conteneur sur le mauvais élément

Une erreur fréquente consiste à déclarer container-type directement sur l’élément que l’on veut modifier.

.card {
  container-type: inline-size;
}

@container (min-width: 500px) {
  .card {
    grid-template-columns: 1fr 1fr;
  }
}

Cette approche ne produit généralement pas le résultat attendu, car une container query cible les descendants du conteneur, pas le conteneur lui-même. Il faut donc placer le conteneur sur un parent.

<div class="card-container">
  <article class="card">
    ...
  </article>
</div>
.card-container {
  container-type: inline-size;
}

@container (min-width: 500px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

Ce détail est important, car il influence directement la structure HTML de vos composants.

Container queries et composants React ou Vue

Dans une application React, Vue, Nuxt ou Next, les container queries réduisent souvent le besoin d’ajouter du JavaScript pour mesurer des éléments. Avant leur adoption, on utilisait parfois [ResizeObserver](/articles/resizeobserver-javascript) pour détecter la taille d’un composant et modifier des classes CSS. Cette solution reste utile dans certains cas avancés, mais elle est souvent excessive pour une simple adaptation visuelle.

Avec les container queries, le CSS reprend son rôle naturel : gérer la présentation.

export function CourseCard() {
  return (
    <div className="course-card-container">
      <article className="course-card">
        <div className="course-card__content">
          <p className="course-card__tag">TypeScript</p>
          <h2>Fiabiliser son frontend</h2>
          <p>Construisez des interfaces plus prévisibles et plus robustes.</p>
        </div>
      </article>
    </div>
  );
}

Le composant React reste simple. Il n’a pas besoin de connaître la largeur de son parent, ni de stocker une information de layout dans un état local.

Bonnes pratiques pour un CSS maintenable

Pour tirer pleinement parti des container queries, évitez de créer trop de seuils. Deux ou trois points de rupture par composant suffisent dans la majorité des cas. Au-delà, le comportement devient difficile à tester.

Privilégiez aussi des seuils liés au contenu plutôt qu’à des appareils. Par exemple, choisissez 520px parce que c’est la largeur à partir de laquelle l’image et le texte respirent correctement, pas parce qu’elle correspond à une tablette particulière.

Enfin, testez vos composants dans plusieurs contextes réels : une grille dense, une colonne étroite, une page large, un état avec beaucoup de texte, un état sans image. Les container queries rendent les composants plus autonomes, mais elles ne dispensent pas de vérifier les cas limites.

Conclusion

Les container queries représentent une évolution majeure du responsive design. Elles permettent de construire des composants plus indépendants, plus prévisibles et mieux adaptés aux architectures frontend modernes.

Elles ne remplacent pas totalement les media queries. Elles les complètent. Les media queries restent utiles pour organiser la page à l’échelle du viewport, tandis que les container queries excellent dans l’adaptation locale des composants.

Pour un développeur web, apprendre à les utiliser correctement permet de produire des interfaces plus solides, notamment dans des projets React, Vue, Nuxt ou Next où les composants sont amenés à vivre dans des contextes très différents.