CSS : `container-type`, unité `cqw` et `clamp()`

Afin d'améliorer la modularité de composants et d'éviter les comportements imprévisibles liés aux écrans, on peut utiliser une approche basée sur les Container Queries. L'objectif : les composants doivent s'adapter à l'espace qu'on leur donne (leur conteneur), et non plus à la taille de l'écran entier (le viewport).


La propriété container-type

Historiquement, les Media Queries (@media (min-width: 768px)) permettaient de changer le design en fonction de la taille de la fenêtre. La propriété container-type permet de créer un contexte local : le composant écoute la taille de son parent direct, pas de l'écran.

Pour que cela fonctionne, on l'applique sur l'élément parent.

Les valeurs possibles :

  • inline-size (le plus utilisé) : Le conteneur observe uniquement les changements sur l'axe "inline" (la largeur, dans les langues occidentales). C'est parfait pour la majorité des composants (cartes, bannières, etc.).
  • size : Le conteneur observe à la fois la largeur et la hauteur. Attention, cela nécessite souvent de définir une hauteur explicite sur le conteneur, sinon il peut s'effondrer.
  • normal : L'élément est défini comme un conteneur pour des requêtes de style (une autre fonctionnalité CSS), mais ne crée pas de contexte de taille.

Exemple :

.card-wrapper {
    /* Cet élément devient la référence de taille pour ses enfants */
    container-type: inline-size;
    container-name: card; /* Optionnel : permet de cibler un conteneur spécifique */
}

Les unités de conteneur (cq*)

Avec un conteneur de référence, nous pouvons utiliser de nouvelles unités de mesure pour ses enfants. Elles fonctionnent exactement comme les unités de viewport (vw, vh), mais relatives au conteneur.

1 unité = 1% de la dimension du conteneur.

  • 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 (équivalent à cqw de gauche à droite).
  • cqb (Container Query Block) : 1% de la dimension block (équivalent à cqh de haut en bas).
  • cqmin : La plus petite valeur entre cqi et cqb.
  • cqmax : La plus grande valeur entre cqi et cqb.

Exemple :

.card-title {
    /* Le titre fera toujours 10% de la largeur de .card-wrapper */
    font-size: 10cqw; 
}

La fonction clamp() : le garde-fou

L'unité cqw est géniale pour l'adaptabilité, mais elle a un défaut : mathématiquement, si le conteneur devient minuscule, le texte devient illisible. S'il devient immense, le texte prend tout l'écran.

C'est là qu'intervient la fonction native CSS clamp(). Elle permet de définir une taille fluide, tout en imposant une borne minimale et une borne maximale.

Syntaxe : clamp(MINIMUM, VALEUR_FLUIDE, MAXIMUM)

Le contexte d'utilisation : On l'utilise pour la typographie fluide, les marges (padding / margin), ou la taille des éléments pour garantir qu'ils restent toujours dans des proportions acceptables, peu importe l'étirement du conteneur.

Exemple pratique :

.card-title {
    /* 1. MIN : Le texte ne descendra jamais en dessous de 1.2rem (lisibilité mobile).
       2. VALEUR : Idéalement, il fait 8% de la largeur du conteneur.
       3. MAX : Il ne dépassera jamais 3rem, même sur un écran ultra-large 4K.
    */
    font-size: clamp(1.2rem, 8cqw, 3rem);
}

En résumé : 1. Définissez container-type: inline-size sur vos parents. 2. Utilisez cqw pour les éléments enfants qui doivent être proportionnels. 3. Sécurisez le tout avec clamp() pour éviter les extrêmes.

Propriété container-name

Par défaut, si on utilise simplement @container (min-width: 500px) ou des unités comme cqw, le navigateur va toujours chercher le conteneur parent le plus proche.

Mais que se passe-t-il si on a un conteneur dans un conteneur (par exemple, une "carte" à l'intérieur de notre fameux "main-container") et que l'élément enfant a besoin de réagir à la taille du grand conteneur principal plutôt qu'à celle de la carte ? C'est là que la propriété container-name entre en jeu.

Définir le nom

Sur l'élément parent, vous définissez le type et le nom.

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

/* 💡 Astuce : on peut utiliser la propriété raccourcie "container" */
.main-container {
    /* Syntaxe : container: <name> / <type>; */
    container: hero-layout / inline-size;
}

Cibler le conteneur nommé avec @container

Pour appliquer des styles conditionnels basés sur la taille de ce conteneur spécifique, on place son nom juste après le mot-clé @container et avant les parenthèses de la condition.

/* Le paragraphe change de couleur SEULEMENT si le conteneur "hero-layout" dépasse 600px */
@container hero-layout (min-width: 600px) {
    p {
        color: var(--primary-color);
        font-weight: bold;
    }
}

Un exemple concret : des conteneurs imbriqués

Imaginons une carte d'article dans notre conteneur principal :

<div class="main-container">
    <div class="article-card">
        <h2 class="title">Mon super titre</h2>
    </div>
</div>
/* 1. Le grand conteneur de la page */
.main-container {
    container: main-layout / inline-size;
}

/* 2. Le petit conteneur de la carte (imbriqué) */
.article-card {
    container: card-layout / inline-size;
}

/* --------------------------------------------------- */
/* CIBLAGE : Le composant enfant choisit à qui il parle */
/* --------------------------------------------------- */

/* Le titre réagit à la taille de sa carte parent direct */
@container card-layout (max-width: 300px) {
    .title {
        font-size: 1.2rem; /* Petit titre si la carte est serrée */
    }
}

/* Mais le titre peut AUSSI réagir à la taille du grand conteneur englobant ! */
@container main-layout (min-width: 1000px) {
    .title {
        /* Si la zone principale est immense, on ajoute un effet spécial, 
           même si la carte elle-même est restée petite. */
        text-transform: uppercase;
        letter-spacing: 2px;
    }
}

⚠️ Point d'attention sur les unités (cqw, cqh) : Actuellement, les unités comme cqw calculent toujours leur valeur en fonction du conteneur le plus proche, même si ce conteneur porte un nom différent. Le container-name sert principalement à diriger les requêtes @container, pas à redéfinir la cible mathématique des unités cqw.