Imbrication CSS

L'une de nos fonctionnalités préférées du préprocesseur CSS est désormais intégrée au langage : l'imbrication des règles de style.

Avant l'imbrication, chaque sélecteur devait être déclaré explicitement, séparément des autres. Cela entraînait des répétitions, un encombrement des feuilles de style et une expérience de création dispersée.

Avant
.nesting {
  color: hotpink;
}

.nesting > .is {
  color: rebeccapurple;
}

.nesting > .is > .awesome {
  color: deeppink;
}

Après l'imbrication, les sélecteurs peuvent être poursuivis et les règles de style associées peuvent être regroupées dans le sélecteur.

Après
.nesting {
  color: hotpink;

  > .is {
    color: rebeccapurple;

    > .awesome {
      color: deeppink;
    }
  }
}

Essayez-le dans le navigateur.

L'imbrication aide les développeurs en réduisant la nécessité de répéter les sélecteurs tout en regroupant les règles de style pour les éléments associés. Elle peut également aider les styles à correspondre au code HTML qu'ils ciblent. Si le composant .nesting de l'exemple précédent était supprimé du projet, vous pourriez supprimer l'ensemble du groupe au lieu de rechercher des instances de sélecteur associées dans les fichiers.

L'imbrication peut vous aider à : - organiser votre code ; - réduire la taille des fichiers ; - refactoriser votre code.

L'imbrication est disponible à partir de Chrome 112 et peut également être essayée dans Safari Technical Preview 162.

Premiers pas avec l'imbrication CSS

Dans le reste de cet article,le bac à sable de démonstration suivant est utilisé pour vous aider à visualiser les sélections. Dans cet état par défaut, rien n'est sélectionné et tout est visible. En sélectionnant les différentes formes et tailles, vous pouvez vous entraîner à utiliser la syntaxe et la voir en action.

Grille colorée composée de petits et grands cercles, triangles et carrés.

Le bac à sable contient des cercles, des triangles et des carrés. Certains sont petits, moyens ou grands. D'autres sont bleus, roses ou violets. Ils se trouvent tous dans l'élément conteneur .demo. Voici un aperçu des éléments HTML que vous allez cibler.

<div class="demo">
  <div class="sm triangle pink"></div>
  <div class="sm triangle blue"></div>
  <div class="square blue"></div>
  <div class="sm square pink"></div>
  <div class="sm square blue"></div>
  <div class="circle pink"></div>
  …
</div>

Exemples d'imbrication

L'imbrication CSS vous permet de définir des styles pour un élément dans le contexte d'un autre sélecteur.

.parent {
  color: blue;

  .child {
    color: red;
  }
}

Dans cet exemple, le .child sélecteur de classe est imbriqué dans le .parent sélecteur de classe. Cela signifie que le sélecteur .child imbriqué ne s'appliquera qu'aux éléments enfants d'éléments ayant une classe .parent.

Cet exemple peut également être écrit à l'aide du symbole &, pour indiquer explicitement où la classe parent doit être placée.

.parent {
  color: blue;

  & .child {
    color: red;
  }
}

Ces deux exemples sont fonctionnellement équivalents. La raison pour laquelle vous avez des options deviendra plus claire à mesure que vous explorerez des exemples plus avancés dans cet article.

Sélectionner les cercles

Dans ce premier exemple, la tâche consiste à ajouter des styles pour estomper et flouter uniquement les cercles de la démonstration.

Sans imbrication, CSS aujourd'hui :

.demo .circle {
  opacity: .25;
  filter: blur(25px);
}

Avec imbrication, il existe deux façons valides de procéder :

/* & is explicitly placed in front of .circle */
.demo {
  & .circle {
    opacity: .25;
    filter: blur(25px);
  }
}

ou

/* & + " " space is added for you */
.demo {
  .circle {
    opacity: .25;
    filter: blur(25px);
  }
}

Résultat : tous les éléments de .demo ayant une classe .circle sont flous et presque invisibles :

La grille colorée de formes ne comporte plus de cercles, qui sont très discrets en arrière-plan.
Essayer une démonstration

Sélectionner tous les triangles et carrés

Cette tâche nécessite de sélectionner plusieurs éléments imbriqués, également appelés sélecteur de groupe.

Sans imbrication, CSS aujourd'hui, il existe deux façons de procéder :

.demo .triangle,
.demo .square {
  opacity: .25;
  filter: blur(25px);
}

ou, en utilisant :is()

/* grouped with :is() */
.demo :is(.triangle, .square) {
  opacity: .25;
  filter: blur(25px);
}

Avec imbrication, voici deux façons valides de procéder :

.demo {
  & .triangle,
  & .square {
    opacity: .25;
    filter: blur(25px);
  }
}

ou

.demo {
  .triangle, .square {
    opacity: .25;
    filter: blur(25px);
  }
}

Résultat : seuls les éléments .circle restent dans .demo :

La grille colorée de formes ne contient plus que des cercles, toutes les autres formes étant presque invisibles.
Essayer une démonstration

Sélectionner les grands triangles et cercles

Cette tâche nécessite un sélecteur composé, dans lequel les éléments doivent avoir les deux classes présentes pour être sélectionnés.

Sans imbrication, CSS aujourd'hui :

.demo .lg.triangle,
.demo .lg.square {
  opacity: .25;
  filter: blur(25px);
}

ou

.demo .lg:is(.triangle, .circle) {
  opacity: .25;
  filter: blur(25px);
}

Avec imbrication, voici deux façons valides de procéder :

.demo {
  .lg.triangle,
  .lg.circle {
    opacity: .25;
    filter: blur(25px);
  }
}

ou

.demo {
  .lg {
    &.triangle,
    &.circle {
      opacity: .25;
      filter: blur(25px);
    }
  }
}

Résultat : tous les grands triangles et cercles sont masqués dans .demo :

Seules les formes petites et moyennes sont visibles dans la grille colorée.
Essayer une démonstration
Conseil de pro avec les sélecteurs composés et l'imbrication

Le symbole & est votre ami ici, car il montre explicitement comment joindre des sélecteurs imbriqués. Prenons l'exemple suivant :

.demo {
  .lg {
    .triangle,
    .circle {
      opacity: .25;
      filter: blur(25px);
    }
  }
}

Bien qu'il s'agisse d'une façon valide d'imbriquer, les résultats ne correspondront pas aux éléments que vous attendez. En effet, sans & pour spécifier le résultat souhaité de .lg.triangle, .lg.circle combinés, le résultat réel serait .lg .triangle, .lg .circle (sélecteurs descendants).

Sélectionner toutes les formes, à l'exception des roses

Cette tâche nécessite une pseudo-classe fonctionnelle de négation, dans laquelle les éléments ne doivent pas avoir le sélecteur spécifié.

Sans imbrication, CSS aujourd'hui :

.demo :not(.pink) {
  opacity: .25;
  filter: blur(25px);
}

Avec imbrication, voici deux façons valides de procéder :

.demo {
  :not(.pink) {
    opacity: .25;
    filter: blur(25px);
  }
}

ou

.demo {
  & :not(.pink) {
    opacity: .25;
    filter: blur(25px);
  }
}

Résultat : toutes les formes qui ne sont pas roses sont masquées dans .demo :

La grille colorée est désormais monochrome et n'affiche que des formes roses.
Essayer une démonstration
Précision et flexibilité avec &

Supposons que vous souhaitiez cibler .demo avec le sélecteur :not(). Dans ce cas, & est obligatoire :

.demo {
  &:not() {
    ...
  }
}

Cela combine .demo et :not() en .demo:not(), contrairement à l'exemple précédent qui nécessitait .demo :not(). Ce rappel est très important lorsque vous souhaitez imbriquer une interaction :hover.

.demo {
  &:hover {
    /* .demo:hover */
  }

  :hover {
    /* .demo :hover */
  }
}

Autres exemples d'imbrication

La spécification CSS pour l'imbrication contient de nombreux autres exemples. Si vous souhaitez en savoir plus sur la syntaxe à l'aide d'exemples, elle couvre un large éventail d'exemples valides et non valides.

Les quelques exemples suivants présentent brièvement une fonctionnalité d'imbrication CSS pour vous aider à comprendre l'étendue des fonctionnalités qu'elle introduit.

Imbriquer @media

Il peut être très distrayant de passer à une autre zone de la feuille de style pour trouver les conditions de requête média qui modifient un sélecteur et ses styles. Cette distraction disparaît grâce à la possibilité d'imbriquer les conditions directement dans le contexte.

Pour plus de commodité, si la requête média imbriquée ne modifie que les styles du contexte de sélecteur actuel, une syntaxe minimale peut être utilisée.

.card {
  font-size: 1rem;

  @media (width >= 1024px) {
    font-size: 1.25rem;
  }
}

Vous pouvez également utiliser & explicitement :

.card {
  font-size: 1rem;

  @media (width >= 1024px) {
    &.large {
      font-size: 1.25rem;
    }
  }
}

Cet exemple montre la syntaxe étendue avec &, tout en ciblant les cartes .large pour démontrer que les fonctionnalités d'imbrication supplémentaires continuent de fonctionner.

En savoir plus sur l'imbrication des @règles.

Imbriquer n'importe où

Tous les exemples jusqu'à présent ont continué ou ajouté un contexte précédent. Vous pouvez modifier ou réorganiser complètement le contexte si nécessaire.

.card {
  .featured & {
    /* .featured .card */
  }
}

Le symbole & représente une référence à un objet sélecteur (et non à une chaîne) et peut être placé n'importe où dans un sélecteur imbriqué. Il peut même être placé plusieurs fois :

.card {
  .featured & & & {
    /* .featured .card .card .card */
  }
}

Bien que cet exemple soit un peu inutile, il existe certainement des scénarios dans lesquels il est pratique de pouvoir répéter un contexte de sélecteur.

Exemples d'imbrication non valides

Il existe quelques scénarios de syntaxe d'imbrication non valides qui peuvent vous surprendre si vous avez déjà utilisé l'imbrication dans des préprocesseurs.

Imbrication et concaténation

De nombreuses conventions d'attribution de noms de classes CSS comptent sur l'imbrication pour pouvoir concaténer ou ajouter des sélecteurs comme s'il s'agissait de chaînes. Cela ne fonctionne pas dans l'imbrication CSS, car les sélecteurs ne sont pas des chaînes, mais des références d'objet.

.card {
  &--header {
    /* is not equal to ".card--header" */
  }
}

Vous trouverez une explication plus détaillée dans la spécification.

Exemple d'imbrication complexe

Imbrication dans les listes de sélecteurs et :is()

Prenons le bloc CSS d'imbrication suivant :

.one, #two {
  .three {
    /* some styles */
  }
}

Il s'agit du premier exemple qui commence par une liste de sélecteurs, puis continue à imbriquer davantage. Les exemples précédents ne se terminaient que par une liste de sélecteurs. Cet exemple d'imbrication n'est pas invalide, mais il existe un détail d'implémentation potentiellement complexe concernant l'imbrication dans les listes de sélecteurs, en particulier celles qui incluent un sélecteur d'ID.

Pour que l'intention de l'imbrication fonctionne, toute liste de sélecteurs qui n'est pas l'imbrication la plus interne sera encapsulée avec :is() par le navigateur. Cette encapsulation maintient le regroupement de la liste de sélecteurs dans tous les contextes créés. L'effet secondaire de ce regroupement, :is(.one, #two), est qu'il adopte la spécificité du score le plus élevé parmi les sélecteurs entre parenthèses. C'est ainsi que :is() toujours fonctionne, mais cela peut être surprenant lorsque vous utilisez la syntaxe d'imbrication, car ce n'est pas exactement ce qui a été créé. En résumé, l'imbrication avec des ID et des listes de sélecteurs peut entraîner des sélecteurs de spécificité très élevée.

Pour récapituler clairement l'exemple complexe, le bloc d'imbrication précédent sera appliqué au document comme suit :

:is(.one, #two) .three {
  /* some styles */
}

Surveillez ou apprenez à vos linters à avertir lorsque vous imbriquez dans une liste de sélecteurs qui utilise un sélecteur d'ID. La spécificité de toute imbrication dans cette liste de sélecteurs sera élevée.

Mélanger l'imbrication et les déclarations

Prenons le bloc CSS d'imbrication suivant :

.card {
  color: green;
  & { color: blue; }
  color: red;
}

La couleur des éléments .card sera blue.

Toutes les déclarations de style intermédaires sont placées en haut, comme si elles avaient été créées avant toute imbrication. Vous trouverez plus de détails dans la spécification.

Il existe des solutions. Le code suivant encapsule les trois styles de couleur dans &, ce qui maintient l'ordre en cascade comme l'auteur l'a peut-être prévu. La couleur des éléments .card sera rouge.

.card {
  color: green;
  & { color: blue; }
  & { color: red; }
}

En fait, il est recommandé d'encapsuler tous les styles qui suivent l'imbrication avec un &.

.card {
  color: green;

  @media (prefers-color-scheme: dark) {
    color: lightgreen;
  }

  & {
    aspect-ratio: 4/3;
  }
}

Détection de fonctionnalités

Il existe deux excellentes façons de détecter les fonctionnalités d'imbrication CSS : utiliser l'imbrication ou utiliser @supports pour vérifier la capacité d'analyse du sélecteur d'imbrication.

Capture d'écran de la démo Codepen de Bramus, qui demande si votre navigateur est compatible avec l'imbrication CSS. Sous cette question se trouve une zone verte, indiquant que l'appareil est pris en charge.

Utiliser l'imbrication :

html {
  .has-nesting {
    display: block;
  }

  .no-nesting {
    display: none;
  }
}

Utiliser @supports :

@supports (selector(&)) {
  /* nesting parsing available */
}

Mon collègue Bramus a créé un excellent Codepen qui illustre cette stratégie.

Déboguer avec les Outils pour les développeurs Chrome

La prise en charge actuelle de l'imbrication dans les outils de développement est minimale. Actuellement, les styles sont représentés comme prévu dans le volet "Styles", mais le suivi de l'imbrication et de son contexte de sélecteur complet n'est pas encore pris en charge. Nous avons des plans pour rendre cela transparent et clair.

Capture d'écran de la syntaxe d'imbrication des outils pour les développeurs Chrome.

Chrome 113 prévoit une prise en charge supplémentaire de l'imbrication CSS. N'hésitez pas à vous tenir informé.

L'avenir

L'imbrication CSS n'en est qu'à la version 1. La version 2 introduira plus de sucre syntaxique et potentiellement moins de règles à mémoriser. La demande est forte pour que l'analyse de l'imbrication ne soit pas limitée ou ne présente pas de difficultés.

L'imbrication est une amélioration majeure du langage CSS. Elle a des implications de création pour presque tous les aspects architecturaux du CSS. Cet impact important doit être exploré et compris en profondeur avant que la version 2 puisse être spécifiée efficacement.

Pour finir, voici une démonstration qui utilise @scope, l'imbrication et @layer ensemble. C'est très intéressant !

Carte claire sur fond gris. La fiche comporte un titre et du texte, quelques boutons d'action et une image de style cyberpunk.