CSS :has() : styliser un parent selon ce qu’il contient
Comprendre le sélecteur relationnel :has(), qui permet enfin d’adapter un élément selon ses descendants ou voisins sans ajouter de classe en JavaScript.
Où en êtes-vous avec cette notion ?
Une indication personnelle, enregistrée uniquement dans ce navigateur.
À lire ensuite
Le format café
L’essentiel à comprendre, le temps d’un café.
Envie de creuser ? Un cours plus complet vous attend juste après, à déplier sans quitter cette page.
L’idée intuitive
Pendant longtemps, CSS savait facilement sélectionner un élément à l’intérieur d’un autre, mais pas remonter vers son parent selon ce qu’il contient.
<article class="card">
<img src="photo.jpg" alt="">
<h2>Un cours</h2>
</article>
Avec :has(), on peut dire : « sélectionne la carte si elle contient une image ».
.card:has(img) {
padding-top: 0;
}
Ce que signifie vraiment :has()
:has() prend un sélecteur comme condition. La règle suivante :
form:has(input:invalid) {
border: 2px solid tomato;
}
se lit : « applique ce style au formulaire s’il contient un champ invalide ».
On peut donc faire réagir un composant à son propre contenu, sans ajouter manuellement une classe comme .has-error en JavaScript.
Il peut aussi regarder les voisins
label:has(+ input:required)::after {
content: " *";
}
Ici, le label est sélectionné lorsque l’élément qui le suit immédiatement est un champ obligatoire.
Un usage très pratique
Pour une liste de cartes dont certaines possèdent une image :
.card {
display: grid;
gap: 1rem;
}
.card:has(> img) {
grid-template-rows: auto 1fr;
}
Le composant adapte sa mise en page à son contenu réel. Le HTML reste sémantique et CSS porte la logique purement visuelle.
Le piège à connaître
:has() augmente la puissance des sélecteurs, donc évitez les conditions inutilement vastes comme body:has(...) répétées partout. Préférez cibler un composant précis et garder des sélecteurs lisibles.
Fiche mémo
.card:has(img): une carte qui contient une image.form:has(:invalid): un formulaire qui contient un contrôle invalide.:has()peut examiner descendants et relations entre voisins.- Il évite parfois du JavaScript utilisé uniquement pour ajouter une classe de présentation.
L’idée à emporter : :has() permet à un élément CSS de réagir à ce qu’il contient ou à ce qui l’entoure ; c’est une petite syntaxe qui rend certains composants beaucoup plus expressifs.
Et si on allait plus loin ?
Le café vous a donné les repères. Prenez maintenant le temps de comprendre les mécanismes et de pratiquer, si vous le souhaitez.
Aller plus loinMaîtriser :has() sans transformer vos sélecteurs en logique applicativeDévelopper le coursReplier le cours
Ce que vous saurez faire
Utiliser :has() pour des composants et formulaires, comprendre sa spécificité et décider quand conserver une classe explicite ou du JavaScript.
1. Une pseudo-classe relationnelle
:has() accepte une liste de sélecteurs relatifs. Le sujet sélectionné reste l’élément placé avant :has().
.product:has(> .badge) {
position: relative;
}
.field:has(> input:focus-visible) {
outline: 2px solid currentColor;
}
Dans le premier cas, seul un enfant direct .badge satisfait la condition. Dans le second, le conteneur peut refléter le focus clavier de son champ sans écouteur JavaScript.
Référence : MDN — :has().
2. La spécificité mérite votre attention
La spécificité de :has() dépend du sélecteur le plus spécifique présent dans sa liste, de manière comparable à :is(). Une règle apparemment courte peut donc devenir difficile à surcharger si elle contient un identifiant ou un sélecteur très spécifique.
Gardez les arguments simples :
/* Facile à raisonner */
.card:has(.error) { }
/* Plus difficile à surcharger sans raison */
.card:has(#checkout-error) { }
Si votre objectif est de réduire volontairement la spécificité, combinez avec :where() selon le besoin plutôt que d’empiler des sélecteurs.
Référence : MDN — Specificity.
3. :has() ne remplace pas l’état métier
Un sélecteur peut observer le DOM, pas votre domaine métier. Pour afficher qu’une commande est réellement payée, une classe ou un attribut issu de l’état applicatif reste plus clair :
<article class="order" data-status="paid">…</article>
.order[data-status="paid"] { /* ... */ }
Chercher indirectement un descendant contenant un texte ou une structure particulière pour déduire ce statut rendrait le CSS fragile. Utilisez :has() lorsque la condition visuelle ou structurelle est déjà présente dans le DOM.
4. Compatibilité et progressive enhancement
:has() est désormais largement disponible dans les navigateurs modernes. Pour un projet devant supporter un navigateur ancien précis, vérifiez néanmoins sa matrice de compatibilité réelle avant d’en faire une dépendance fonctionnelle.
Vous pouvez aussi détecter la prise en charge :
@supports selector(.card:has(img)) {
.card:has(img) {
/* amélioration progressive */
}
}
La page conserve alors une présentation de base même lorsque cette amélioration n’est pas disponible.
Pièges importants
- Ne déduisez pas un état métier important uniquement de la structure visuelle du DOM.
- Surveillez la spécificité des sélecteurs placés dans
:has(). - Préférez un composant ciblé à une recherche très large depuis
bodyou une racine globale. - Une validation CSS avec
:invalidaméliore l’interface mais ne remplace jamais la validation côté serveur.
Mémo final
Utilisez :has() pour exprimer une relation que CSS peut réellement observer : « ce composant contient ceci », « ce champ voisin est obligatoire », « ce groupe contient un contrôle invalide ». Gardez JavaScript et les attributs explicites pour les états qui viennent de la logique applicative.
Vous pouvez aussi vous arrêter ici. L’approfondissement est facultatif.
Les repères Cours Café
Pour aller à la source
Documentation de référence. Vérification éditoriale encore à effectuer.
Gardez une trace de cette idée.
Favoris, notes et progression seront disponibles après connexion du stockage distant.