# :has() en CSS pour signaler un champ requis vide sans une ligne de JavaScript

> Comparée à un script de validation classique, la pseudo-classe :has() permet de styliser un fieldset contenant un champ invalide en pur CSS, avec quelques réserves de compatibilité.

- Auteur : WordPress Développement
- Publié le : 2023-02-03
- Mis à jour le : 2023-02-03
- Catégorie : Accessibilité
- URL : https://www.wpmoderne.fr/accessibilite/has-css-champ-requis-vide-sans-javascript/

## L’essentiel

- :has() sélectionne un parent selon son contenu
- Le style s'applique sans écouter le moindre événement
- Un repli reste nécessaire pour les navigateurs qui ne le supportent pas encore

Face à un champ de formulaire requis laissé vide, deux approches s'opposent depuis toujours : écouter l'événement de validation en JavaScript et ajouter une classe à la main, ou laisser le CSS observer directement l'état natif `:invalid` du champ. La pseudo-classe `:has()` ouvre une troisième voie, moins connue : styliser le parent — ici un `fieldset` — en fonction de ce qu'il contient, sans jamais toucher au script.

Cette recette montre comment signaler visuellement un regroupement de champs contenant une erreur de validation, uniquement en CSS. Elle ne traite pas de la rédaction des messages d'erreur eux-mêmes, qui font l'objet d'un tout autre sujet, à savoir la manière de les formuler et de les annoncer.

## Le problème posé

Sur un formulaire d'inscription, un `fieldset` regroupe trois champs liés à l'adresse postale. Si l'un des trois est requis et resté vide à la tentative de soumission, l'intégrateur souhaite entourer l'ensemble du regroupement d'un liseré visuellement distinct, en plus du message d'erreur associé à chaque champ. Une solution classique ajouterait un écouteur `submit` en JavaScript, qui parcourt les champs invalides et ajoute une classe sur leur `fieldset` parent :

```
document.querySelector( 'form' ).addEventListener( 'submit', function ( evenement ) {
  document.querySelectorAll( 'fieldset' ).forEach( function ( groupe ) {
    var contientInvalide = groupe.querySelector( ':invalid' );
    groupe.classList.toggle( 'wpm-groupe-erreur', Boolean( contientInvalide ) );
  } );
} );
```

Ce script fonctionne, mais il ajoute une dépendance à un événement précis, et doit être dupliqué ou adapté pour chaque nouveau type d'interaction — une saisie en temps réel, par exemple, demanderait d'écouter aussi `input` sur chaque champ.

## La solution en pur CSS avec :has()

La pseudo-classe `:has()` sélectionne un élément si l'un de ses descendants correspond au sélecteur passé en argument. Appliquée à un `fieldset`, elle permet de le cibler directement s'il contient un champ à l'état natif `:invalid`, sans écouteur d'événement :

```
fieldset:has(:invalid) {
  border-color: #b3261e;
  background-color: #fdf2f1;
}

fieldset:has(:invalid) legend {
  color: #b3261e;
  font-weight: 600;
}
```

L'état `:invalid` reflète en continu la validité native du champ, telle que le navigateur la calcule à partir des attributs `required`, `pattern` ou `type="email"`. Le style du `fieldset` se met donc à jour automatiquement, à chaque frappe, sans qu'aucun script n'ait besoin d'écouter quoi que ce soit.

> L'essentiel à retenir : :has() sélectionne un parent selon son contenu ; Le style s'applique sans écouter le moindre événement ; Un repli reste nécessaire pour les navigateurs qui ne le supportent pas encore

## La réserve de compatibilité qui compte

En janvier 2023, deux moteurs de rendu majeurs supportent `:has()` en production : Safari depuis la version 15.4, et les navigateurs basés sur Chromium depuis la version 105. Firefox, à cette date, ne le supporte pas encore en dehors d'un indicateur expérimental. Présenter `:has()` comme universellement disponible serait inexact : il s'agit d'une amélioration progressive, à réserver aux navigateurs qui la comprennent, jamais du seul mécanisme de signalement d'erreur.

## Le repli pour les navigateurs qui ne supportent pas :has()

La requête de fonctionnalité `@supports` permet d'isoler proprement la règle, et de la faire cohabiter avec le script de repli déjà montré plus haut, qui continue de fonctionner partout :

```
@supports selector(:has(*)) {
  fieldset:has(:invalid) {
    border-color: #b3261e;
    background-color: #fdf2f1;
  }
}
```

Avec cette structure, un navigateur qui comprend `:has()` applique le style enrichi sans exécuter le moindre JavaScript supplémentaire ; un navigateur qui ne le comprend pas encore continue de recevoir le même signalement, via le script `submit` classique, sans dépendre d'une fonctionnalité CSS absente.

## Variantes utiles

La même pseudo-classe permet aussi de cibler un `label` associé à un champ invalide, à condition que la structure HTML le permette, ou de styliser une ligne de tableau contenant une cellule en erreur dans un formulaire tabulaire. Une variante fréquente ajoute également un état positif, pour confirmer visuellement qu'un groupe de champs est désormais valide après correction :

```
fieldset:has(:valid):not(:has(:invalid)) {
  border-color: #2e7d32;
}
```

> Conseil maison : sur un projet où le support de Firefox reste indispensable à court terme, garder le script JavaScript comme mécanisme principal et traiter `:has()` comme un simple raffinement visuel évite toute divergence de comportement entre navigateurs.

## Notre verdict

Comparée à l'écoute d'événements JavaScript, la pseudo-classe `:has()` simplifie nettement le signalement visuel d'un groupe de champs en erreur, en s'appuyant directement sur l'état de validité natif du navigateur. Elle mérite sa place dans une feuille de style dès aujourd'hui, à condition de l'envelopper dans une requête `@supports` et de conserver un mécanisme de repli tant que l'ensemble des navigateurs ciblés ne l'a pas rattrapée.
