</> HTML5Advent
ENFRESDEITPT

// css · Web Platform Advent #17

Le sélecteur CSS :has() : le sélecteur de parent, expliqué (2026)

La pseudo-classe relationnelle :has() permet de styliser un élément en fonction de ce qu'il contient ou de ce qui le suit - le sélecteur de parent tant attendu. Comment ça marche, exemples concrets, compatibilité des navigateurs et les pièges.

Gros plan de lignes de code CSS colorées sur un écran d'ordinateur

La pseudo-classe :has() est la fonctionnalité que les auteurs CSS réclamaient depuis deux décennies : un moyen de styliser un élément en fonction de ce qui se trouve à l'intérieur, ou de ce qui vient après. On l'appelle souvent le "sélecteur de parent", même si elle est en réalité bien plus puissante que cela - c'est un sélecteur relationnel. Il est désormais présent dans tous les navigateurs modernes, vous pouvez donc l'utiliser en production dès aujourd'hui.

L'idée de base

Un sélecteur CSS classique cible un élément par son propre type, sa classe ou son état. :has() inverse la logique : il cible un élément à cause des éléments qu'il contient. On écrit le parent, puis :has(...) avec un sélecteur pour ce qui doit se trouver à l'intérieur.

/* Style any .card that contains an <img> */
.card:has(img) {
  padding: 0;
}

/* Style a <figure> only when it has a caption */
figure:has(figcaption) {
  border: 1px solid #ddd;
}

L'élément qui est réellement stylisé est celui qui se trouve avant :has() - la .card et le figure, pas l'image ni la légende à l'intérieur. C'est toute l'astuce : la condition porte sur les descendants, mais la cible est l'ancêtre.

C'est relationnel, pas seulement "parent"

Comme vous pouvez placer des combinateurs à l'intérieur de :has(), il fait bien plus que regarder les enfants. Vous pouvez réagir aux frères et sœurs, ce qui était impossible en CSS auparavant.

/* A heading that is immediately followed by a paragraph */
h2:has(+ p) {
  margin-bottom: 0.25rem;
}

/* A form that contains an invalid field */
form:has(input:invalid) {
  border-left: 3px solid crimson;
}

/* A label whose related required input is empty and focused */
.field:has(input:required:placeholder-shown) .hint {
  opacity: 1;
}

Ce deuxième exemple est typiquement le genre de chose pour laquelle on avait l'habitude de recourir à JavaScript : changer l'apparence de tout un formulaire (ou de n'importe quel conteneur) en fonction de l'état d'un contrôle à l'intérieur. Avec :has(), c'est une seule ligne de CSS, et cela se met à jour en direct à mesure que l'utilisateur saisit du texte.

Gros plan de code HTML et CSS affiché sur un écran
:has() lit la relation entre les éléments, de sorte qu'un conteneur peut réagir au contenu et à l'état qu'il renferme - sans aucun JavaScript.

Cas d'usage pratiques

  • Requêtes de quantité et de contenu : styliser une galerie différemment selon qu'elle contient exactement une image ou plusieurs - .gallery:has(img:only-child).
  • Style du frère précédent : le CSS ne pouvait regarder que vers l'avant avec + et ~ ; :has() permet à un élément antérieur de réagir à un élément ultérieur, par exemple label:has(+ input:checked).
  • Mise en page qui s'adapte au contenu : donner à une carte une disposition sur deux colonnes uniquement lorsqu'elle contient réellement une image, et sur une seule colonne dans le cas contraire.
  • États de formulaire et de validation : mettre en évidence tout un fieldset lorsqu'un contrôle à l'intérieur est invalide, sans une seule ligne de script.

Compatibilité des navigateurs

La prise en charge est large en 2026. :has() est arrivé dans Chrome et Edge 105 (août 2022) et Safari 15.4, Firefox rejoignant le mouvement à la version 121 (décembre 2023). Cela signifie qu'il est désormais disponible dans les versions actuelles de tous les principaux navigateurs, et qu'on peut s'appuyer dessus en toute sécurité pour la plupart des publics. Pour les navigateurs plus anciens, vous pouvez traiter les styles :has() comme une amélioration progressive, puisqu'un navigateur qui ne comprend pas le sélecteur ignore simplement la règle.

Pièges à connaître

  • Vous ne pouvez pas cibler de pseudo-éléments à l'intérieur de :has(). Les sélecteurs comme ::before ne sont pas autorisés comme argument.
  • :has() lui-même n'est pas autorisé à l'intérieur de :has(). Vous ne pouvez pas imbriquer une vérification relationnelle dans une autre.
  • Gardez une portée raisonnable. Les sélecteurs très larges comme body:has(...) qui forcent le navigateur à réévaluer de grandes parties de la page peuvent coûter en performance sur les documents volumineux et fréquemment mis à jour. En pratique, les sélecteurs limités à des composants ne posent pas de problème ; les moteurs ont optimisé les cas courants.
  • La cible est le sujet, pas la correspondance. Si une règle ne s'applique pas, vérifiez que vous stylisez bien l'élément avant :has(), et non celui à l'intérieur.

FAQ

:has() est-il vraiment le sélecteur de parent CSS ? En pratique oui, et bien plus encore. Il sélectionne un ancêtre en fonction de ses descendants, ce qui correspond au cas d'usage classique du "sélecteur de parent", mais comme il accepte les combinateurs, il peut aussi sélectionner en fonction des frères suivants, ce qui le décrit mieux comme un sélecteur relationnel.

Puis-je utiliser :has() en production en 2026 ? Pour la plupart des sites, oui - il est pris en charge dans les versions actuelles de tous les principaux navigateurs. Utilisez-le comme une amélioration progressive si vous devez prendre en charge de très vieux navigateurs.

:has() nuit-il aux performances ? Rarement, si vous gardez les sélecteurs limités. Les vérifications :has() extrêmement larges, à l'échelle du document, sur de grandes pages en changement constant peuvent ajouter un coût, mais l'usage au niveau des composants ne pose pas de problème.

Pour d'autres techniques CSS modernes, consultez nos guides sur l'imbrication CSS native et les requêtes de conteneur.