La validation des formulaires HTML est largement sous-exploitée
(expressionstatement.com)- Les formulaires HTML disposent déjà de fonctions de validation natives comme
required,type="email",pattern,maxlengthetsetCustomValidity, mais leur usage réel reste faible au regard de l’étendue de ces possibilités - Le plus puissant,
setCustomValidity, peut gérer une logique de validation arbitraire et des cas complexes, mais il n’est proposé que comme méthode DOM, ce qui s’accorde mal avec les composants déclaratifs - Dans des environnements comme React, aligner la validation avec la valeur initiale implique souvent d’entremêler
useRef,useLayoutEffectetonChange, et la même logique de validation tend à être dupliquée entre le rendu initial et le gestionnaire de changement - Avec une abstraction sous forme d’attribut comme
custom-validity, on pourrait exprimer au même endroit des validations dépendantes de l’état et de l’entrée, comme la vérification asynchrone de disponibilité d’un nom d’utilisateur ou la confirmation de mot de passe - La faible adoption de la validation native des formulaires tient moins à un manque de fonctionnalités qu’à un problème d’ergonomie de l’API ; si une API déclarative entrait dans la spécification HTML, son usage pourrait nettement progresser
Ce que la validation des formulaires HTML fournit déjà
- La façon la plus simple d’empêcher une saisie vide consiste à ajouter l’attribut
required - On peut imposer des contraintes de saisie de trois grandes façons
- utiliser des types de champ comme
type="email",type="number"outype="url" - utiliser des attributs de contrainte comme
patternoumaxlength - utiliser la méthode DOM
setCustomValiditydu champ
- utiliser des types de champ comme
setCustomValidityest le moyen le plus puissant, car il permet de traiter une logique de validation arbitraire et des cas complexes- Les deux premières approches peuvent être déclarées via des attributs HTML, alors que
setCustomValidityexige un appel de méthode - Une ressource de référence sur la différence entre attributs et propriétés DOM est également liée : Attributes vs Properties
Là où setCustomValidity devient peu pratique
setCustomValidityn’est exposé que comme méthode, sans attribut HTML correspondant- Si on lui passe une chaîne, le champ devient invalide et le navigateur affiche cette chaîne comme raison de l’échec de validation
- Si on lui passe une chaîne vide, le champ redevient valide en l’absence d’autres contraintes
- Pour réimplémenter soi-même un comportement comme
required, il faut appelersetCustomValidityà chaque changement de valeur - Toutefois, un champ est valide au départ ; si l’on clique sur le bouton d’envoi juste après avoir réinitialisé le composant, la soumission du formulaire peut passer
- car même si la valeur est vide, le code de validation ne s’exécute pas tant qu’aucun événement de changement n’a eu lieu
- pour couvrir aussi la valeur initiale, il faut donc exécuter la même validation au montage du composant
Le boilerplate qui s’accumule dans les composants déclaratifs
- Dès qu’il faut aussi gérer la validation de la valeur initiale, le code devient vite répétitif et peu confortable
- Trois problèmes apparaissent en particulier
- la logique de validation est dupliquée entre le gestionnaire
onChangeet l’étape de rendu initial - le code de validation initiale est séparé de l’élément de saisie, ce qui réduit la cohésion et crée un risque de ne modifier qu’un seul côté
- la combinaison
useRef,useLayoutEffectetonChangedevient excessive à mesure que le nombre de champs augmente, et encore plus déroutante quand seuls certains champs utilisentcustomValidity
- la logique de validation est dupliquée entre le gestionnaire
- Cette complexité est particulièrement visible lorsqu’on manipule une API impérative pure dans des composants déclaratifs
CustomValidityne dispose d’aucun attribut de champ permettant de définir sa valeur de façon déclarative, contrairement aux attributs de validation natifs- Quand une API est pénible à utiliser, l’adoption peut rester faible même si la fonctionnalité est puissante ; c’est là la cause principale de la sous-utilisation de la validation native des formulaires
La pièce manquante : l’attribut custom-validity
- La forme nécessaire serait un attribut déclaratif tel que
custom-validity, directement définissable sur le champ - Dans un framework déclaratif, cet attribut permettrait de garder l’état de validation au plus près de l’élément de saisie
- À l’heure actuelle, la
spécification HTMLne contient pas de véritablecustom-validity - Il est possible d’imiter ce comportement via une implémentation côté utilisateur à des fins de démonstration
- Un exemple d’implémentation plus abouti est aussi fourni pour un composant de production
Validation asynchrone et dépendances entre champs
- Dans une application réelle, la validation peut devenir plus complexe que de simples vérifications locales
- Un champ de nom d’utilisateur doit vérifier côté serveur si le nom est déjà utilisé, et pendant la requête le formulaire ne doit pas être dans un état valide
- L’exemple utilise
requiredpour empêcher une saisie vide, puis rend le champ invalide viacustomValidityselon l’état de chargement et le résultat de la réponse - L’implémentation se compose de deux parties
- l’état de la requête de vérification d’unicité du nom d’utilisateur est géré avec
useQuerydereact-query - un composant personnalisé
<Input />capable de recevoir une propcustomValidityest utilisé
- l’état de la requête de vérification d’unicité du nom d’utilisateur est géré avec
- Cette approche permet d’exprimer dans un seul attribut l’ensemble du flux de validation asynchrone, y compris les états de chargement, d’erreur et de succès
- Un autre exemple montre un formulaire qui demande de ressaisir le mot de passe, en traitant la validation de champs dépendants les uns des autres
Conclusion
setCustomValidityest un outil puissant capable de répondre à des besoins de validation variés- L’usage réel d’une fonctionnalité dépend non seulement de son existence, mais aussi d’une API qui la rende facile à utiliser
- Une abstraction déclarative comme
custom-validitypermettrait d’utiliser plus naturellement la validation native des formulaires - On peut espérer qu’une telle fonctionnalité entre un jour nativement dans la spécification HTML
1 commentaires
Avis de Hacker News
La dernière fois que j’ai vérifié, les navigateurs web modernes ne permettaient toujours pas de styliser l’apparence des messages de validation HTML intégrés https://stackoverflow.com/questions/5328883/how-do-i-style-t...
Ce serait moins gênant si Chrome et Firefox suivaient au moins les directives d’interface de la plateforme de l’OS, de sorte qu’ils ressemblent à quelque chose d’affiché par le système, comme les infobulles
title="". Mais Chrome utilise une icône jaune/orange, du texte noir sur fond blanc et une bulle aux coins arrondis fixes, ce qui jure fortement avec l’esthétique du projet en coursLes anciennes versions de Chrome permettaient de styliser les messages de validation via des sélecteurs de pseudo-éléments préfixés par le fournisseur, mais après avoir supprimé cette fonctionnalité, ils ne l’ont jamais réintroduite. Ça rejoint ma liste arbitraire d’agacements du genre « donnez-nous une vraie combobox HTML native » ou « pourquoi a-t-on encore cette boîte Ctrl+clic pénible à utiliser au lieu d’une liste de cases à cocher »
validitynatif d’une entrée et de l’afficher comme on veutLe vrai problème, c’est qu’il est délicat de s’abonner précisément aux changements de cet état de validité. Il existe bien des événements de validation, mais ils ne se déclenchent pas toujours. Si l’on modifie programmatiquement l’état du formulaire, par exemple avec un appel à
form.reset()ouinput.value = '...', ces événements ne sont pas déclenchésJe pense que c’est un bon sujet à étudier séparément et à proposer à la plateforme web
selectsimple n’est pas aussi facile qu’il devrait l’êtreLe plus gros gain, facile à implémenter mais sous-utilisé, consiste à employer des valeurs d’attribut
typeprécises commeemail,numberouurlSur mobile, cela peut afficher le clavier optimal et améliorer fortement l’expérience utilisateur
type=numberet j’utilise plutôttype=text inputmode=numeric. On n’a pas les flèches dont la plupart des utilisateurs n’ont pas besoin pour saisir des nombres, et le clavier est aussi meilleur sur iOStypedate. Chaque bibliothèque frontend a son propre widget de date, et beaucoup rendent très mal sur les petits écransÀ cause de ce seul widget, il faut ajouter du JS et du CSS, et certains dépendent de jQuery. Bien sûr, certains sont très configurables, mais pour un coût minime on peut obtenir partout un widget correct, d’apparence native, qui répond généralement aux besoins, et qu’on peut oublier sans se soucier des mises à jour ou d’un CDN
C’est déjà tout juste possible avec une entrée classique, mais les entrées spécialisées prennent en charge encore moins d’événements
Mon produit n’a pas passé l’audit d’accessibilité parce qu’il utilisait la validation native des formulaires HTML, et la recommandation officielle était d’implémenter notre propre couche de validation. Je suis d’accord avec cette recommandation.
La validation native HTML a beaucoup de défauts et, franchement, la personnalisation visuelle n’est même pas mon principal souci. C’est toutefois assez proche du dernier clou dans le cercueil.
Par exemple, si l’on veut afficher plusieurs erreurs par champ en une seule fois, la seule option est de concaténer des chaînes. Du genre : « Un chiffre est requis. Un symbole est requis. Doit faire plus de 10 caractères », ce qui donne une mauvaise expérience utilisateur et n’est pas bon non plus pour l’accessibilité. En effet, on ne peut pas naviguer dans une chaîne concaténée dans l’arbre d’accessibilité. Ce n’est pas un problème propre à une implémentation, mais à la spécification elle-même. Les utilisateurs n’aiment pas jouer à la chasse aux erreurs de validation, il faut donc pouvoir afficher plusieurs erreurs en une seule fois.
La dépendance au navigateur est aussi un problème. On ne contrôle rien et les implémentations sont globalement médiocres. Chrome affiche une pop-up à la prise de focus ; c’est une pop-up, elle ressemble à une modale, et comme elle ne peut pas afficher toutes les erreurs du formulaire à la fois, elle est en soi peu accessible. Ne pas afficher des informations importantes dans une pop-up est une base de l’accessibilité, mais le navigateur n’a pas vraiment d’autre moyen de faire sans perturber le document réel.
Les erreurs qui concernent l’ensemble du formulaire et pas un champ précis nécessitent toujours une validation custom. Par exemple : « Les champs A et B ne sont pas compatibles ». À ce stade, autant avoir une méthode de validation cohérente.
Si vous avez une saisie custom qui ne correspond pas aux types d’entrée natifs, il faut ajouter un champ caché pour garder une certaine cohérence, ce qui casse aussi l’accessibilité. Corriger cela finit par être aussi difficile que de construire son propre système de validation compatible avec l’accessibilité.
L’API
customValidityest impérative et pénible à utiliser. À moins de vouloir des messages horribles du type « Ce champ est invalide », ne pas utiliser de validité custom n’est quasiment pas une option. C’est pour cela que la validation des formulaires HTML est mauvaise.Je pense quand même qu’il vaut la peine d’exploiter le mécanisme de validation natif. Pour les messages d’erreur, il n’est pas nécessaire d’utiliser l’infobulle de validité native : on peut lire directement le
ValidityStatedeinput.validity, puis rendre les messages comme on le souhaite et afficher plusieurs erreurs si nécessaire.Les navigateurs peuvent s’améliorer, et en utilisant une approche standardisée on peut bénéficier de ces améliorations. Une conclusion du genre « si l’on n’utilise pas de pop-up, l’accessibilité est cassée » semble étrange, mais si l’objectif est de répondre à un audit, il faut peut-être en passer par là.
Il existe aussi des techniques pour gérer les erreurs qui ne relèvent pas d’un champ précis. Dans mon projet, nous utilisons un composant
HiddenValidationInputqui crée un champ invisible en lecture seule afin de rendre les erreurs custom qui ne sont rattachées à aucun autre champ. Il suffit de le rendre conditionnellement, et en pratique c’est assez agréable à utiliser.Je suis totalement d’accord sur le fait que l’API
customValidityest impérative et pénible. J’ai écrit un article qui traite précisément ce point, et j’espère que cela s’améliorera avec le temps.Le design grossier et peu accessible de Chrome est un problème de Chrome, donc je vais ouvrir un rapport de bug.
Quand vous parlez de « spécification », c’est bien de ceci qu’il s’agit ? https://html.spec.whatwg.org/multipage/dom.html#concept-elem...
Je ne vois pas de partie définissant comment les messages de validation doivent être affichés. Il n’est même pas indiqué qu’un message doit obligatoirement exister.
Honnêtement, les gens qui écrivent ce genre de spécifications semblent déconnectés de la réalité et ne pas utiliser eux-mêmes ce qu’ils spécifient. Ça fonctionne pour des choses très simples, mais dès que les formulaires commencent à évoluer, on finit par comprendre qu’il vaut mieux tout écrire soi-même.
À partir du moment où l’on commence à utiliser du JS, il devient beaucoup plus simple de tout faire en code plutôt que de bricoler avec les attributs de validation.
Si une case à cocher a un libellé, j’aimerais qu’on mette un attribut
forsur le libellé pour permettre d’activer ou de désactiver la case en cliquant dessus. C’est personnellement l’une des choses qui m’agacent le plus, même si je suis peut-être le seul.inputdans lelabel, et cela fonctionne. Je ne sais pas trop pourquoi les gens ont tendance à les séparer.Et je ne sais pas non plus pourquoi les navigateurs ont commencé à dissocier ces éléments. Selon moi, ce n’est pas le texte qui devrait contenir les cases à cocher et les boutons radio, mais les cases à cocher et les boutons radio qui devraient contenir le texte.
Voici un exemple simple qui n’utilise pas React :
https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectE...
La validation des formulaires HTML est excellente. Mais elle comporte un énorme piège
Elle ne fonctionne pas dans Firefox pour Android
https://bugzilla.mozilla.org/show_bug.cgi?id=1510450
Cela ne vaut pas la peine d’ignorer le standard pour moins de 1 % d’utilisateurs qui ne voient pas les messages d’erreur des contrôles affichés en rouge. Plus de sites web l’utiliseront, plus Mozilla subira de pression pour corriger son navigateur
Ce n’est pas une API comme WebMIDI, implémentée par Chrome ou Safari avant la standardisation, mais une partie de la spécification HTML5 d’origine
C’est d’autant plus vrai quand on pense au peu de temps consacré à permettre aux personnes utilisant des logiciels d’accessibilité d’utiliser correctement les sites. À moins d’être prêt à parler aussi d’UC Browser, je ne pense pas que ce problème mérite vraiment d’être mentionné
Cela dit, pouvoir utiliser uBlock Origin avec d’autres extensions reste un atout majeur
[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1884282
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=1897707
Si la validation n’avait simplement pas été implémentée, cela aurait presque été préférable, mais là c’est vraiment catastrophique. Je l’ai découvert il y a quelques années après un débogage long et pénible, et je suis certain de ne pas avoir été le premier ni le dernier à vivre la même chose
Avant de chercher, je n’imaginais pas ce que j’avais pu rater, et c’était assez choquant. Faire sa propre validation, c’est acceptable, mais ça, ça devrait fonctionner
Il faut faire attention à ne pas en abuser
J’ai récemment essayé d’obtenir un remboursement auprès de Groupon. L’entreprise chez qui j’avais acheté le Groupon avait changé de direction et refusait de reconnaître mon Groupon
Le formulaire imposait une condition « au moins 15 mots », mais impossible de passer la validation, quoi que je fasse, et j’ai fini par devoir inspecter le HTML
\wcorrespondait aux caractères de mot,\baux limites de mot,\saux espaces, et la ponctuation n’était tout simplement pas autorisée, littéralementpatternsont intéressants, mais insuffisantsPar exemple, l’exemple de « répétition du mot de passe » peut être implémenté avec l’attribut
patternsanssetCustomValidity. Pour cela, il faut construire dynamiquement une expression régulière à partir de la première valeur saisieJe n’ai pas comparé les solutions pour éviter que l’article ne devienne trop long, mais l’idée centrale est qu’avec
customValidity, la validation devient beaucoup plus claire et lisible. Ici, une meilleure API fait une grande différenceLa contrainte « au moins 15 mots » pourrait aussi être exprimée de façon bien plus lisible, par exemple avec
value.split(/\s+/).length >= 15Il y a une raison si c’est moins utilisé. Beaucoup de frameworks et de bibliothèques fournissent des fonctionnalités de validation robustes et stylables, certaines étant très sophistiquées et extensibles. Si l’on peut éviter de se compliquer la vie, mieux vaut ne pas s’infliger ça
curl, ou un autre outil qui n’intègre pas la même validation de formulaireOn ne peut pas faire confiance au client pour savoir s’il a validé les données, ni s’il l’a fait correctement ; le backend doit donc toujours valider les entrées et pouvoir réafficher le formulaire avec les erreurs de validation pour tous les champs
La validation frontend sert seulement à aider l’utilisateur. Mais si l’on veut la styliser ou la déclencher côté backend à la soumission, on finit de toute façon par devoir implémenter son propre style
Il y a trop de pièges, et pour prendre facilement en charge des vérifications plus complexes, on finit par utiliser une bibliothèque
En plus, utiliser une bibliothèque ouvre parfois la possibilité de partager une partie du code de validation entre le frontend et le backend
En particulier, cet article semble contourner un problème avec
useLayoutEffect, ce qui n’est pas quelque chose à faire à la légèreUne des choses que je n’aime pas dans la validation des formulaires HTML, c’est qu’elle s’exécute dès le chargement de la page. Par exemple, si l’on y associe les styles d’état d’erreur, le formulaire se charge dès le départ rempli d’erreurs, ce qui peut paraître intimidant pour l’utilisateur
:user-invalid, qui permet d’éviter cela dans une certaine mesure. Elle manque toutefois de flexibilité, et peut ne pas suffire selon le cas d’usagehttps://developer.mozilla.org/en-US/docs/Web/CSS/:user-inval...
On peut le contourner avec des scripts, mais à ce stade cette fonctionnalité n’apporte plus grand-chose. Je pense que c’est la principale raison pour laquelle elle n’est pas adoptée plus largement