CSS :has(), la pseudo-class

Introduction Introduction

Depuis longtemps, on a souhaité pouvoir appliquer le style sur un élément en fonction de ses enfants. Ceci est enfin possible depuis l’ajout du support de la pseudo-class :has() par tous les principaux navigateurs récents.

Le besoin initial Le besoin initial

Pour illustrer le besoin, nous allons prendre l’exemple d’une carte qui contient du texte et potentiellement une image. On souhaite effectuer un affichage différent de la carte si celle-ci possède une image ou non.

Card with or without avatar

Lorsque le composant Card ne possède pas d’image :

Lorsque le composant Card possède une image, la disposition doit changer :

Jusqu’à présent, la seule solution que l’on avait était de créer une classe spécifique où on l’on anticipait le fait d’avoir une image.

Démo card with class modifier

My title card

Depuis longtemps, on a souhaité pouvoir appliquer le style sur un élément en fonction de ses enfants.

avatar

My title card with image

Depuis longtemps, on a souhaité pouvoir appliquer le style sur un élément en fonction de ses enfants.

Voir sur codepen

.card {
  /* style de base */
}
.card--with-image {
  /* style de surcharge */
}

Cela nous oblige à anticiper le fait d’avoir une image dans le DOM et à ajouter une classe sur notre élément HTML pour pouvoir appliquer notre CSS.

La solution avec :has() La solution avec :has()

Avec la nouvelle pseudo-classe :has(), il n’est désormais plus nécessaire d’ajouter une classe sur notre élément parent. Voici ce que cela donnerait avec la nouvelle pseudo-classes :has() :

.card {
  /* style de base */
}
.card:has(img) {
  /* style de surcharge */
}

Démo card with :has()

My title card

Depuis longtemps, on a souhaité pouvoir appliquer le style sur un élément en fonction de ses enfants.

avatar

My title card with image

Depuis longtemps, on a souhaité pouvoir appliquer le style sur un élément en fonction de ses enfants.

Voir sur codepen

C’est un peu le cas classique d’utilisation où l’on va juste vérifier la présence d’un élément enfant que soit le degré de parenté.

Spécifique layout

On remarque qu’il est tout à fait possible de modifier complètement le comportement et l’agencement des enfants d’un élément en fonction de leur présence ou non. Par exemple avec les grid-template-areas :

.card:has(h2 + img + p + p) {
  grid-template-areas: 
    "img img"
    "h2 h2"
    "p1 p2"
}
.card:has(h2 + img + img + p + p) {
  grid-template-areas: 
    "h2 h2"
    "img1 img2"
    "p1 p2"
}

Les cas basiques Les cas basiques

Le cumul strict du :has() (ET LOGIQUE) Le cumul strict du :has() (ET LOGIQUE)

On peut cumuler les :has() afin d’être plus strict sur la possession des différents sélecteurs d’enfants. Cela restreint le ciblage car l’élément parent devra posséder tous les enfants.

/* Cible les éléments de class .card qui possèdent une image ET un h2 */

.card:has(img):has(h2) {
  /* style */
}

Le cumul souple du :has() (ET/OU LOGIQUE) Le cumul souple du :has() (ET/OU LOGIQUE)

Si on souhaite cibler des éléments qui ont l’un des enfants ou tous les enfants

/* Cible les éléments de class .card qui possèdent une image ET/OU un h2 */

.card:has(img, h2) {
  /* style */
}

Sélection inverse du :has() (NON LOGIQUE) Sélection inverse du :has() (NON LOGIQUE)

Si on souhaite cibler des éléments qui ne possèdent pas un enfant, on peut combiner le :has() avec un :not() :

/* Cible les éléments de class .card qui ne possèdent pas une image */

.card:not(:has(img)) {
  /* style */
}

Sélection globale du :has() Sélection globale du :has()

Un autre cas d’usage classique est de pouvoir appliquer du style sur n’importe quel élément de la page en fonction de la présence ou non d’un autre élément dans la page.

Par exemple, on pourrait gérer un système de thèmes dark/light mode avec une checkbox dans le header du site. Et si la case est cochée, on est en dark mode et inversement. Lorsque je suis en dark mode, je souhaite appliquer un certain style au footer de page.

C’est ce principe qui a été utilisé pour ce site.

body:has(input.myCheckBoxTheme:checked) footer {
  /* style */
}

Le prédécesseur Le prédécesseur

Sélectionner l’élément précédé par : Sélectionner l’élément précédé par :

Parmi les sélecteurs combinateurs, il existe le sélecteur + qui permet de cibler un élément précédé d’un autre.

Par exemple, un h2 suivi d’un p, on va pouvoir appliquer du style sur les paragraphes qui sont précédés d’un titre de niveau 2 :

h2 + p {
  /* style  */
}

Sélectionner l’élément suivi par : Sélectionner l’élément suivi par :

Si on combine le :has() avec le sélecteur combinateur +, on va pouvoir inverser le ciblage pour appliquer du style sur le prédécesseur:

Par exemple, un span qui est suivi par un autre span :

😀 😀 😀 😀 😀 😕

Dans cet exemple, on constate que seul le dernier élément n’est pas ciblé car il n’est pas suivi d’un span.

Voir sur codepen

Sélectionner tous les éléments suivi par : Sélectionner tous les éléments suivi par :

On peut également combiner le :has() avec le sélecteur combinateur ~, on va pouvoir cibler tous les éléments prédécesseurs à un autre élément même si celui-ci est éloigné.

Dans cet exemple, on a une série de labels avec un bouton radio pour enfant. Si un bouton radio est coché, alors on souhaite que tous les labels précédents aient une couleur jaune.

Voir sur codepen

Sibling scope

En réalité, la combinaison :has() et du ~ permet de définir un sorte d’intervalle de ciblage des éléments adjacents :

.from ~ :has(~ .to) {
  background: rgba(255, 255, 255, 0.8);
  color: #333;
}
  • outside
  • from
  • in
  • in
  • in
  • in
  • to
  • outside

C’est tout pour la première partie des possibilités avec le :has(), pour voir la suite pour cliquer le lien de la partie 2 de l’article

Les sources Les sources

Retour à la liste des articles
Github de Samuel Gomez Linkedin de Samuel Gomez Twitter de Samuel Gomez Instagram de Samuel Gomez
Allez en haut