Intégration web angularforall.com

- CSS Container Queries : responsive par composant

Css Container-Queries Responsive Composants Css-Moderne
CSS Container Queries : responsive par composant

Maîtrisez les CSS Container Queries pour des composants vraiment responsives. Syntaxe, exemples pratiques et comparaison avec les Media Queries.

Introduction — la révolution du responsive par composant

Depuis l'avènement du responsive design, les développeurs utilisent les Media Queries pour adapter les interfaces à la taille de l'écran. Cette approche fonctionne bien pour les mises en page globales — mais elle révèle ses limites dès qu'on travaille avec des composants réutilisables.

Le problème fondamental : un composant ne sait pas dans quel contexte il sera intégré. Une carte produit placée dans une sidebar de 300 px devrait s'afficher différemment de la même carte en pleine largeur. Avec les Media Queries, impossible d'y parvenir sans hacks CSS fragiles.

Les CSS Container Queries résolvent élégamment ce problème. Introduites dans la spécification CSS Containment Level 3, elles permettent à un élément de réagir à la taille de son conteneur parent — et non plus à celle de la fenêtre. Chaque composant devient ainsi autonome, véritablement portable et réutilisable dans n'importe quel contexte.

Ce que vous allez apprendre dans cet article :
  • Comprendre les limites des Media Queries pour les composants
  • Maîtriser la syntaxe container-type, container-name et @container
  • Créer des composants qui s'adaptent à leur contexte d'intégration
  • Utiliser les unités cqw, cqh, cqi, cqb
  • Combiner Container Queries et Bootstrap 5
  • Gérer le support navigateurs avec progressive enhancement

Limites des Media Queries traditionnelles

Pour bien comprendre l'apport des Container Queries, illustrons d'abord le problème classique que vous avez certainement rencontré.

Le problème du composant contextuel

Imaginons une carte d'article qui doit s'afficher :

  • En colonne (image au-dessus, texte dessous) quand elle est dans une sidebar étroite
  • En ligne (image à gauche, texte à droite) quand elle occupe une largeur confortable
/* ❌ Approche Media Queries — problème classique */

/* La carte passe en mode ligne à partir de 768px de viewport */
@media (min-width: 768px) {
    .article-card {
        display: flex;
        flex-direction: row;
    }

    .article-card img {
        width: 200px;
        flex-shrink: 0;
    }
}

/* Mais si la carte est dans une sidebar de 320px sur un écran 1200px...
   La media query passe en mode "row" à cause des 1200px de viewport,
   même si le conteneur disponible est trop étroit pour ce layout !
   Résultat : la carte est écrasée et illisible. */

Les contournements habituels — fragiles et non maintenables

/* ❌ Hack classique : classes utilitaires spécifiques au contexte */

/* On doit créer des variantes pour chaque contexte de placement */
.sidebar .article-card {
    /* Styles pour la sidebar */
    flex-direction: column;
}

.main-content .article-card {
    /* Styles pour le contenu principal */
    flex-direction: row;
}

.featured-section .article-card {
    /* Encore un autre contexte... */
    flex-direction: row;
    font-size: 1.2rem;
}

/* Résultat :
   - Le composant n'est plus indépendant
   - Il connaît son contexte parent (couplage fort)
   - Difficile à maintenir et à tester
   - Impossible à réutiliser sans modifications */
Le principe fondamental : Un composant bien conçu doit être autonome — il ne devrait pas avoir connaissance de son environnement. Les CSS Container Queries permettent exactement cela : le composant observe son propre conteneur, pas le viewport global.

Syntaxe de base des Container Queries

Les Container Queries reposent sur deux éléments : déclarer un conteneur sur l'élément parent, puis utiliser @container pour définir des styles conditionnels sur ses enfants.

Etape 1 — Déclarer un conteneur avec container-type

/* Déclarer un élément comme conteneur CSS */

/* container-type: inline-size
   → Observe la largeur inline (horizontale) du conteneur
   → Le cas d'usage le plus courant pour le responsive */
.card-wrapper {
    container-type: inline-size;
}

/* container-type: size
   → Observe la largeur ET la hauteur du conteneur
   → Attention : nécessite une hauteur explicite sur le conteneur */
.widget-wrapper {
    container-type: size;
}

/* container-type: normal (valeur par défaut)
   → L'élément ne répond pas aux @container queries
   → Utile pour désactiver explicitement le comportement */
.no-container {
    container-type: normal;
}

Etape 2 — Nommer le conteneur avec container-name

/* Nommer un conteneur pour le cibler précisément */

/* Sans nom : @container cible le conteneur parent le plus proche */
.card-wrapper {
    container-type: inline-size;
    /* Pas de nom — le @container le plus proche sera utilisé */
}

/* Avec nom : permet de cibler un conteneur ancêtre spécifique */
.sidebar {
    container-type: inline-size;
    container-name: sidebar; /* Nom sémantique et lisible */
}

.main-layout {
    container-type: inline-size;
    container-name: main-layout;
}

/* Raccourci avec la propriété shorthand container */
.card-wrapper {
    /* container:  /  */
    container: card-context / inline-size;
}

Etape 3 — Appliquer des styles avec @container

/* Syntaxe de base : @container (condition) { styles } */

/* Cibler le conteneur parent le plus proche */
@container (min-width: 400px) {
    /* Styles appliqués quand le CONTENEUR fait >= 400px de large */
    .article-card {
        display: flex;
        flex-direction: row;
        gap: 1rem;
    }
}

/* Cibler un conteneur nommé spécifiquement */
@container sidebar (min-width: 280px) {
    /* Appliqué seulement quand le conteneur "sidebar" fait >= 280px */
    .article-card {
        padding: 1rem;
    }
}

/* Conditions disponibles pour @container */
@container (width >= 400px)         { /* Largeur min */ }
@container (width <= 600px)         { /* Largeur max */ }
@container (400px <= width <= 800px){ /* Plage de largeur */ }
@container (height >= 300px)        { /* Hauteur (si container-type: size) */ }
@container (aspect-ratio >= 1)      { /* Ratio largeur/hauteur */ }
@container (orientation: landscape) { /* Orientation */ }
Note syntaxe moderne : La nouvelle syntaxe de comparaison (width >= 400px) est équivalente à min-width: 400px mais plus lisible. Les deux fonctionnent dans tous les navigateurs qui supportent les Container Queries.

Exemples pratiques — card, sidebar, navigation

Exemple 1 — Card produit responsive par composant

La card produit classique qui passe de l'affichage colonne (étroit) à l'affichage ligne (large), indépendamment du viewport.

/* ========================================
   HTML — Structure de la card produit
   ======================================== */
<!-- Wrapper conteneur : c'est lui qui est observé -->
<div class="product-card-container">
    <article class="product-card">
        <!-- Image du produit -->
        <div class="product-card__image">
            <img src="product.webp" alt="Nom du produit" width="300" height="200">
        </div>
        <!-- Contenu textuel -->
        <div class="product-card__body">
            <h3 class="product-card__title">Nom du produit</h3>
            <p class="product-card__description">Description courte du produit.</p>
            <div class="product-card__footer">
                <span class="product-card__price">29,99 €</span>
                <button class="btn btn-primary btn-sm">Ajouter au panier</button>
            </div>
        </div>
    </article>
</div>
/* ========================================
   CSS — Container Query sur la card
   ======================================== */

/* 1. Déclarer le wrapper comme conteneur */
.product-card-container {
    container-type: inline-size;
    container-name: product-card;
    /* Le conteneur peut avoir n'importe quelle largeur selon son placement */
}

/* 2. Styles par défaut — mode colonne (petit espace) */
.product-card {
    display: flex;
    flex-direction: column; /* Image au-dessus, texte dessous */
    background-color: var(--color-bg-card, #fff);
    border: 1px solid var(--color-border, #dee2e6);
    border-radius: 10px;
    overflow: hidden;
}

.product-card__image img {
    width: 100%;
    height: 180px;
    object-fit: cover; /* Recadrage intelligent de l'image */
    display: block;
}

.product-card__body {
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.product-card__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: auto; /* Colle le footer en bas */
}

/* 3. Quand le CONTENEUR fait plus de 420px → mode ligne */
@container product-card (min-width: 420px) {
    .product-card {
        flex-direction: row; /* Image à gauche, texte à droite */
    }

    .product-card__image {
        width: 200px;    /* Largeur fixe pour l'image */
        flex-shrink: 0;  /* Empêche l'image de rétrécir */
    }

    .product-card__image img {
        width: 200px;
        height: 100%;    /* L'image prend toute la hauteur de la card */
    }

    .product-card__body {
        padding: 1.25rem;
    }
}

/* 4. Mode large — quand le conteneur dépasse 600px */
@container product-card (min-width: 600px) {
    .product-card__title {
        font-size: 1.4rem; /* Titre plus grand sur grand espace */
    }

    .product-card__description {
        display: block;    /* Afficher la description (masquée en petit) */
        font-size: 1rem;
    }

    .product-card__image {
        width: 280px; /* Image plus large */
    }
}

Exemple 2 — Sidebar qui passe en layout horizontal

/* ========================================
   Sidebar adaptative avec Container Query
   ======================================== */

/* Déclarer la sidebar comme conteneur */
.sidebar-container {
    container-type: inline-size;
    container-name: sidebar;
}

/* Mode vertical par défaut (sidebar étroite) */
.sidebar-nav {
    display: flex;
    flex-direction: column; /* Menu vertical */
    gap: 0.25rem;
    padding: 1rem 0;
}

.sidebar-nav__item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 1rem;
    border-radius: 8px;
    text-decoration: none;
    color: var(--color-text, #212529);
    transition: background-color 0.2s;
}

.sidebar-nav__item:hover {
    background-color: var(--color-bg-tertiary, #e9ecef);
}

/* Icône toujours visible */
.sidebar-nav__icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* Label du menu : visible seulement si espace suffisant */
.sidebar-nav__label {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Quand la sidebar est trop étroite : masquer les labels, centrer les icônes */
@container sidebar (max-width: 200px) {
    .sidebar-nav__item {
        justify-content: center; /* Centrer l'icône horizontalement */
        padding: 0.75rem;
    }

    .sidebar-nav__label {
        display: none; /* Masquer le texte, garder uniquement l'icône */
    }

    /* Tooltip au survol pour compenser l'absence de label */
    .sidebar-nav__item[title]:hover::after {
        content: attr(title);
        position: absolute;
        left: 100%;
        top: 50%;
        transform: translateY(-50%);
        background: #333;
        color: #fff;
        padding: 0.25rem 0.5rem;
        border-radius: 4px;
        font-size: 0.8rem;
        white-space: nowrap;
        margin-left: 0.5rem;
    }
}

/* Quand la sidebar est assez large : affichage en grille */
@container sidebar (min-width: 480px) {
    .sidebar-nav {
        flex-direction: row;  /* Menu horizontal */
        flex-wrap: wrap;
    }

    .sidebar-nav__item {
        flex: 1 1 auto; /* Occupe l'espace disponible équitablement */
    }
}

Exemple 3 — Navigation qui change de layout selon le conteneur

/* ========================================
   Widget de navigation contextuelle
   ======================================== */
<!-- Même composant, utilisable dans header ET dans footer -->
<div class="nav-container">
    <nav class="contextual-nav" aria-label="Navigation principale">
        <a href="/" class="nav-item">
            <svg class="nav-icon" aria-hidden="true"><!-- icône accueil --></svg>
            <span class="nav-label">Accueil</span>
        </a>
        <a href="/articles" class="nav-item">
            <svg class="nav-icon" aria-hidden="true"><!-- icône articles --></svg>
            <span class="nav-label">Articles</span>
        </a>
        <a href="/outils" class="nav-item">
            <svg class="nav-icon" aria-hidden="true"><!-- icône outils --></svg>
            <span class="nav-label">Outils</span>
        </a>
    </nav>
</div>
/* Conteneur de la nav */
.nav-container {
    container-type: inline-size;
    container-name: nav-context;
}

/* Styles de base — affichage compact (icône + label empilés) */
.contextual-nav {
    display: flex;
    gap: 0.5rem;
}

.nav-item {
    display: flex;
    flex-direction: column;  /* Icône au-dessus du label */
    align-items: center;
    gap: 0.25rem;
    padding: 0.5rem;
    text-decoration: none;
    color: var(--color-text, #212529);
    font-size: 0.75rem;
    border-radius: 6px;
    transition: background-color 0.2s;
}

/* Navigation en ligne (icône + label côte à côte) si espace > 400px */
@container nav-context (min-width: 400px) {
    .nav-item {
        flex-direction: row; /* Icône à gauche du label */
        gap: 0.5rem;
        font-size: 0.9rem;
        padding: 0.5rem 0.75rem;
    }
}

/* Navigation avec fond et style étendu si espace > 700px */
@container nav-context (min-width: 700px) {
    .contextual-nav {
        gap: 0.25rem;
    }

    .nav-item {
        padding: 0.6rem 1.25rem;
        font-size: 1rem;
        font-weight: 500;
    }

    .nav-item:hover {
        background-color: rgba(13, 110, 253, 0.08); /* Bleu translucide au survol */
    }
}
A retenir : Le même composant .contextual-nav peut maintenant être utilisé dans un header large, un footer compact, ou une sidebar étroite. Il s'adapte automatiquement sans aucune connaissance de son contexte d'intégration.

Container Queries vs Media Queries

Container Queries et Media Queries ne s'opposent pas — elles se complètent. Voici un tableau comparatif pour savoir laquelle utiliser selon le contexte.

Critère Media Queries Container Queries
Référence observée Viewport (fenêtre du navigateur) Conteneur parent de l'élément
Cas d'usage idéal Mise en page globale, layout de page Composants réutilisables, widgets
Portabilité des composants Faible — dépend du viewport Forte — indépendant du contexte
Syntaxe @media (min-width: 768px) @container (min-width: 400px)
Déclaration préalable Aucune — fonctionne immédiatement Requiert container-type sur le parent
Support navigateurs Universel (IE 9+) Chrome 105+, Firefox 110+, Safari 16+ (2023)
Performance Très bonne (calcul au niveau global) Bonne (calcul par conteneur déclaré)
Applicabilité aux fonctionnalités OS Oui (prefers-color-scheme, etc.) Non (uniquement dimensionnel/style)

Quand utiliser quoi ?

/* ✅ MEDIA QUERIES — pour la mise en page globale */

/* Changer le nombre de colonnes selon la taille de l'écran */
.page-grid {
    display: grid;
    grid-template-columns: 1fr; /* 1 colonne sur mobile */
}

@media (min-width: 768px) {
    .page-grid {
        grid-template-columns: 300px 1fr; /* sidebar + contenu sur tablette */
    }
}

@media (min-width: 1200px) {
    .page-grid {
        grid-template-columns: 280px 1fr 240px; /* 3 colonnes sur desktop */
    }
}

/* ✅ CONTAINER QUERIES — pour les composants dans ces colonnes */

/* La card s'adapte à la colonne qui la contient */
.card-wrapper {
    container-type: inline-size;
}

@container (min-width: 350px) {
    .article-card {
        flex-direction: row; /* Horizontal quand la colonne est assez large */
    }
}

/* Les deux techniques travaillent ensemble harmonieusement :
   Media Queries définissent la structure globale,
   Container Queries gèrent le comportement interne des composants. */

Cas d'usage avancés — Style Queries et unités cq*

Les unités de conteneur (cqw, cqh, cqi, cqb)

Analogues aux unités viewport (vw, vh), les unités de conteneur permettent de dimensionner les éléments en pourcentage de leur conteneur — pas du viewport.

/* Unités de conteneur disponibles */

/* cqw  — Container Query Width  : 1% de la largeur du conteneur */
/* cqh  — Container Query Height : 1% de la hauteur du conteneur */
/* cqi  — Container Query Inline : 1% de la dimension inline (largeur en mode LTR) */
/* cqb  — Container Query Block  : 1% de la dimension block (hauteur en mode LTR) */
/* cqmin — Minimum des deux dimensions (inline et block) */
/* cqmax — Maximum des deux dimensions */

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

.card-title {
    /* La taille de police s'adapte à la largeur du conteneur */
    /* cqi = container query inline size (= largeur en LTR) */
    font-size: clamp(1rem, 4cqi, 2rem);
    /* clamp(min, préféré, max) :
       - Minimum : 1rem (ne descend jamais en dessous)
       - Préféré : 4% de la largeur du conteneur
       - Maximum : 2rem (ne monte jamais au-dessus) */
}

.card-image {
    /* L'image prend 30% de la largeur du conteneur */
    width: 30cqi;
    min-width: 120px; /* Mais jamais moins de 120px */
    max-width: 300px; /* Et jamais plus de 300px */
}

/* Application pratique : typographie fluide par composant */
.hero-card {
    container-type: inline-size;
}

.hero-card__title {
    /* Titre qui grandit proportionnellement au conteneur */
    font-size: clamp(1.5rem, 6cqw, 4rem);
    line-height: 1.2;
}

.hero-card__subtitle {
    font-size: clamp(0.9rem, 2.5cqw, 1.5rem);
}

Style Queries — réagir aux valeurs de propriétés CSS

Les Style Queries sont une extension des Container Queries permettant d'appliquer des styles selon la valeur d'une propriété CSS custom (variable) du conteneur.

/* Style Queries — syntaxe @container style() */

/* Déclarer des variables de contexte sur le conteneur */
.product-card-container {
    container-type: inline-size;
    container-name: product;
    /* Variable de contexte : statut du produit */
    --product-status: available;
}

.product-card-container.out-of-stock {
    --product-status: out-of-stock;
}

.product-card-container.on-sale {
    --product-status: on-sale;
}

/* Modifier le composant selon la valeur de la variable */
@container product style(--product-status: out-of-stock) {
    /* Styles pour les produits épuisés */
    .product-card {
        opacity: 0.6;         /* Aspect grisé */
        filter: grayscale(1); /* Désaturation */
    }

    .product-card__price::after {
        content: ' — Épuisé';
        color: #dc3545; /* Rouge Bootstrap */
        font-weight: bold;
    }
}

@container product style(--product-status: on-sale) {
    /* Styles pour les promotions */
    .product-card {
        border-color: #ffc107; /* Bordure jaune */
        box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.3); /* Lueur jaune */
    }

    .product-card__price {
        color: #dc3545;       /* Prix en rouge */
        font-weight: 800;     /* Gras accentué */
    }
}
Note support : Les Style Queries (@container style()) sont supportées dans Chrome 111+ et Safari 18+. Firefox les supporte depuis la version 129. Pour une production 2024, elles nécessitent encore un fallback ou une utilisation progressive.

Intégration avec Bootstrap 5

Bootstrap 5 utilise des Media Queries pour sa grille. Les Container Queries s'ajoutent au-dessus pour affiner le comportement interne des composants. Les deux coexistent parfaitement.

Exemple — Card Bootstrap dans une grille adaptative

<!-- Grille Bootstrap 5 : définit le nombre de colonnes via Media Queries -->
<div class="row g-3">
    <!-- Colonne principale : 8/12 sur md, 12/12 sur sm -->
    <div class="col-12 col-md-8">
        <!-- Wrapper conteneur CSS pour les cards internes -->
        <div class="content-card-container">
            <article class="content-card">
                <img src="article.webp" alt="Titre de l'article" class="content-card__img">
                <div class="content-card__body">
                    <h3 class="content-card__title">Titre de l'article</h3>
                    <p class="content-card__excerpt">Extrait de l'article...</p>
                    <a href="#" class="btn btn-primary btn-sm mt-2">Lire la suite</a>
                </div>
            </article>
        </div>
    </div>

    <!-- Sidebar : 4/12 sur md, cachée sur sm -->
    <div class="col-12 col-md-4 d-none d-md-block">
        <!-- Même wrapper conteneur mais largeur différente -->
        <div class="content-card-container">
            <article class="content-card">
                <img src="article.webp" alt="Titre de l'article" class="content-card__img">
                <div class="content-card__body">
                    <h3 class="content-card__title">Titre de l'article</h3>
                    <p class="content-card__excerpt">Extrait de l'article...</p>
                    <a href="#" class="btn btn-primary btn-sm mt-2">Lire la suite</a>
                </div>
            </article>
        </div>
    </div>
</div>
/* CSS — Container Query sur le composant card */

/* Le wrapper est déclaré conteneur */
.content-card-container {
    container-type: inline-size;
    container-name: content-card;
    height: 100%; /* Pour que les cards aient la même hauteur dans la grille */
}

/* Styles par défaut — mode colonne (adapté à une colonne étroite) */
.content-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    border: 1px solid var(--bs-border-color, #dee2e6); /* Variable Bootstrap 5 */
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;
    background-color: var(--bs-body-bg, #fff);
}

.content-card__img {
    width: 100%;
    height: 180px;
    object-fit: cover;
    display: block;
}

.content-card__body {
    padding: 1rem;
    flex: 1; /* Prend l'espace restant */
    display: flex;
    flex-direction: column;
}

.content-card__excerpt {
    flex: 1;
    color: var(--bs-secondary-color, #6c757d);
    margin-bottom: 0;
}

/* Quand le conteneur est large (> 450px) :
   La colonne principale passe en mode ligne,
   la sidebar reste en mode colonne (trop étroite) — automatiquement ! */
@container content-card (min-width: 450px) {
    .content-card {
        flex-direction: row; /* Mode ligne pour la colonne principale */
    }

    .content-card__img {
        width: 220px;   /* Largeur fixe pour l'image */
        height: auto;   /* Hauteur automatique */
        flex-shrink: 0;
    }

    .content-card__title {
        font-size: 1.25rem;
    }
}

/* Bootstrap 5 gère le layout global (nombre de colonnes)
   Container Query gère le comportement interne de chaque composant
   → Séparation propre des responsabilités */

Composant réutilisable dans sidebar ET en pleine largeur

/* Démonstration : même composant, deux contextes différents,
   comportement adaptatif automatique */

/* Contexte 1 : Grille Bootstrap — col-12 col-md-4 (sidebar ~33%) */
/* Contexte 2 : Grille Bootstrap — col-12 (pleine largeur) */

/* Le composant n'a besoin d'aucune classe contextuelle :
   il s'adapte seul grâce à container-type sur son wrapper */

/* Résultats observés :
   - Dans col-md-4 (~380px sur écran 1200px) : mode colonne (image + texte empilés)
   - Dans col-12 (> 1100px) : mode ligne avec image à gauche et texte à droite
   - Sur mobile (< 768px, Bootstrap col-12) : mode colonne adaptatif
   → Comportement parfaitement cohérent sans CSS supplémentaire */

Support navigateurs et fallbacks

Etat du support en 2025

Navigateur Container Queries Unités cq* Style Queries
Chrome / Edge 105+ (2022) 105+ (2022) 111+ (2023)
Firefox 110+ (2023) 110+ (2023) 129+ (2024)
Safari 16+ (2022) 16+ (2022) 18+ (2024)
Opera 91+ (2022) 91+ (2022) 97+ (2023)
IE / Edge Legacy Non supporté Non supporté Non supporté
Couverture globale > 93% > 93% ~85%

Feature detection avec @supports

/* Détecter le support des Container Queries avant de les utiliser */

/* Approche 1 : @supports CSS — fallback intégré */
@supports (container-type: inline-size) {
    /* Ce bloc est exécuté SEULEMENT si les Container Queries sont supportées */
    .card-wrapper {
        container-type: inline-size;
    }

    @container (min-width: 400px) {
        .article-card {
            flex-direction: row;
        }
    }
}

/* Styles de fallback (sans @supports) : s'appliquent à tous les navigateurs */
.article-card {
    /* Layout par défaut — mobile first, pas de flex-direction: row */
    display: flex;
    flex-direction: column;
}

/* Fallback avec Media Queries pour les anciens navigateurs */
@media (min-width: 640px) {
    /* Sera surchargé par les Container Queries si supportées */
    .article-card {
        flex-direction: row;
    }
}

@supports (container-type: inline-size) {
    /* Annule le fallback Media Query si Container Queries disponibles */
    @media (min-width: 640px) {
        .article-card {
            flex-direction: column; /* Réinitialise pour laisser place aux @container */
        }
    }
}

Feature detection en JavaScript

/* Vérifier le support depuis JavaScript */

// Méthode 1 : CSS.supports()
const containerQueriesSupported = CSS.supports('container-type', 'inline-size');

if (containerQueriesSupported) {
    console.log('Container Queries supportées — comportement natif actif');
    document.documentElement.classList.add('cq-supported');
} else {
    console.log('Container Queries non supportées — fallback JS actif');
    document.documentElement.classList.add('cq-unsupported');
    // Charger un polyfill ou adapter le comportement
}

// Méthode 2 : Vérifier via un élément de test
function supportsContainerQueries() {
    const div = document.createElement('div');
    div.style.containerType = 'inline-size';
    return div.style.containerType === 'inline-size';
}

// Polyfill (si nécessaire pour les anciens navigateurs)
// https://github.com/GoogleChromeLabs/container-query-polyfill
// <script src="https://cdn.jsdelivr.net/npm/container-query-polyfill@1/dist/container-query-polyfill.modern.js"></script>
Polyfill disponible : Google Chrome Labs maintient un polyfill officiel pour les Container Queries disponible sur npm : container-query-polyfill. Il permet de supporter les navigateurs plus anciens de façon transparente, bien qu'il ne soit généralement pas nécessaire en 2025 pour la majorité des projets.

Bonnes pratiques et organisation CSS

1. Ne déclarer que les conteneurs nécessaires

/* ❌ Mauvaise pratique : déclarer container-type sur tout */
* {
    container-type: inline-size; /* Crée un contexte de containment partout — coûteux */
}

/* ❌ Trop généreux : sur des éléments qui n'en ont pas besoin */
body, main, section, div, article, aside {
    container-type: inline-size;
}

/* ✅ Bonne pratique : uniquement sur les wrappers des composants adaptatifs */
.card-wrapper,
.widget-container,
.sidebar-nav-wrapper,
.product-grid-item {
    container-type: inline-size;
}

/* ✅ Explication : container-type crée un contexte d'isolation CSS
   qui peut affecter les z-index et le stacking context.
   Ne l'appliquer qu'aux éléments qui en ont réellement besoin. */

2. Nommer les conteneurs de façon sémantique

/* ❌ Noms génériques ou absents — confusion sur les grandes bases de code */
.wrapper-1 { container-type: inline-size; container-name: c1; }
.wrapper-2 { container-type: inline-size; container-name: c2; }
.wrapper-3 { container-type: inline-size; container-name: c3; }

/* ✅ Noms sémantiques alignés avec le rôle du composant */
.product-card-wrapper {
    container-type: inline-size;
    container-name: product-card;  /* Décrit le composant contenu */
}

.sidebar-widget-wrapper {
    container-type: inline-size;
    container-name: sidebar-widget; /* Décrit le contexte */
}

.hero-banner-wrapper {
    container-type: size; /* size pour observer hauteur ET largeur */
    container-name: hero-banner;
}

/* Convention recommandée : kebab-case, nom descriptif du composant ou contexte */

3. Organiser les breakpoints de façon cohérente

/* Définir des tokens de breakpoints conteneur dans une section dédiée */

/* ========================================
   Tokens — Breakpoints par composant
   ======================================== */

/* Card compacte : change à partir de 320px */
/* Card standard : change à partir de 400px et 600px */
/* Card héro : change à partir de 480px et 800px */

/* Garder les @container groupés avec leur composant */

/* ========================================
   Composant : .article-card
   ======================================== */
.article-card-wrapper {
    container-type: inline-size;
    container-name: article-card;
}

/* Base : mode colonne */
.article-card { /* ... styles de base ... */ }

/* Premier breakpoint : 400px */
@container article-card (min-width: 400px) {
    .article-card { /* ... ajustements ... */ }
}

/* Deuxième breakpoint : 700px */
@container article-card (min-width: 700px) {
    .article-card { /* ... styles large ... */ }
}

4. Combiner clamp() et unités cq* pour la typographie fluide

/* Technique avancée : typographie qui s'adapte au conteneur */

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

.fluid-card__title {
    /* font-size fluide : min 1rem, préféré 5% de la largeur conteneur, max 2rem */
    font-size: clamp(1rem, 5cqi, 2rem);

    /* cqi = container query inline size
       Sur un conteneur de 200px : 5cqi = 10px → clamp force 1rem (16px)
       Sur un conteneur de 400px : 5cqi = 20px → valeur fluide
       Sur un conteneur de 600px : 5cqi = 30px → clamp force 2rem (32px) */
}

.fluid-card__excerpt {
    font-size: clamp(0.875rem, 3cqi, 1.125rem);
    line-height: 1.5;
}

/* Padding fluide selon le conteneur */
.fluid-card__body {
    padding: clamp(0.75rem, 4cqi, 2rem);
}
Checklist avant de déployer vos Container Queries :
  • container-type déclaré uniquement sur les wrappers nécessaires
  • Noms de conteneurs sémantiques et cohérents
  • Fallback via @supports pour les navigateurs non supportés
  • Styles par défaut (sans @container) fonctionnels en mobile-first
  • Testé dans Chrome DevTools avec redimensionnement du conteneur
  • Aucun container-type sur les éléments ayant des z-index critiques
  • Unités cq* utilisées avec clamp() pour éviter les valeurs extrêmes
  • Style Queries utilisées avec prudence (support partiel en 2025)

Conclusion et ressources

Les CSS Container Queries représentent une évolution fondamentale du responsive design. En permettant aux composants de réagir à leur propre conteneur plutôt qu'au viewport global, elles ouvrent la voie à une architecture CSS véritablement modulaire, où chaque composant est autonome et portable dans n'importe quel contexte d'intégration.

Avec un support navigateur dépassant 93% en 2025, les Container Queries sont prêtes pour la production. La stratégie recommandée est simple : commencez par les adopter sur les nouveaux composants en mobile-first, ajoutez un @supports pour la robustesse, et combinez-les avec Bootstrap 5 pour la mise en page globale.

A retenir : Media Queries et Container Queries sont complémentaires — les premières gouvernent la mise en page globale, les secondes gèrent le comportement interne des composants. Utilisées ensemble, elles forment le duo parfait pour un responsive design robuste, maintenable et véritablement orienté composant.

Points clés à retenir

  • container-type: inline-size sur le wrapper — active l'observation de la largeur
  • container-name: mon-composant — permet de cibler précisément un conteneur ancêtre
  • @container (min-width: 400px) — réagit à la largeur du conteneur, pas du viewport
  • Unités cqw, cqi — dimensionnement relatif au conteneur
  • Style Queries @container style() — adapter le style selon des variables CSS custom
  • Combiner avec clamp() pour une typographie fluide par composant
  • Toujours écrire les styles par défaut en mobile-first — les @container enrichissent ensuite

Ressources recommandées

Partager