Subutilização da validação de formulários em HTML
(expressionstatement.com)- Formulários HTML já contam com recursos nativos de validação como
required,type="email",pattern,maxlengthesetCustomValidity, mas seu uso real é baixo em relação à amplitude desses recursos - O mais poderoso,
setCustomValidity, consegue lidar com lógica de validação arbitrária e casos complexos, porém é exposto apenas como método do DOM, o que não combina bem com componentes declarativos - Em ambientes como React, para alinhar a validação do valor inicial,
useRef,useLayoutEffecteonChangeacabam se entrelaçando, e a mesma lógica de validação tende a ser duplicada entre a renderização inicial e o manipulador de mudanças - Se existisse uma abstração em forma de atributo como
custom-validity, seria possível expressar em um só lugar validações com dependência entre estado e entrada, como verificação assíncrona de nome de usuário duplicado ou confirmação de senha - A baixa adoção da validação nativa de formulários parece estar mais ligada a um problema de usabilidade da API do que à falta de recursos, e um API declarativo na especificação HTML ampliaria bastante seu potencial de uso
O que a validação de formulários HTML já oferece
- A forma mais simples de impedir entradas vazias é adicionar o atributo
required - Há três maneiras principais de impor restrições à entrada
- usar tipos de entrada como
type="email",type="number"etype="url" - usar atributos de restrição como
patternemaxlength - usar o método DOM
setCustomValidityda entrada
- usar tipos de entrada como
setCustomValidityé o recurso mais poderoso, pois pode lidar com lógica de validação arbitrária e casos complexos- As duas primeiras formas podem ser declaradas como atributos HTML, mas
setCustomValidityexige uma chamada de método - Como material de referência sobre a diferença entre atributos e propriedades do DOM, há um link para Attributes vs Properties
Onde setCustomValidity começa a ficar incômodo
setCustomValidityé exposto apenas como método, sem um atributo HTML correspondente- Ao passar uma string, a entrada se torna inválida, e o navegador mostra essa string como motivo da falha de validação
- Ao passar uma string vazia, a entrada volta a ser válida, desde que não haja outras restrições
- Para implementar manualmente um comportamento como
required, é preciso chamarsetCustomValiditysempre que o valor mudar - Porém, a entrada começa inicialmente em estado válido, então, se o botão de envio for clicado logo após redefinir o componente, o envio do formulário pode passar
- isso acontece porque, mesmo com o valor vazio, o código de validação não é executado antes de ocorrer um evento de mudança
- para alinhar também o valor inicial, é necessário executar a mesma validação no momento em que o componente é montado
Mais boilerplate em componentes declarativos
- Quando é preciso lidar também com a validação do valor inicial, o código rapidamente se torna repetitivo e desconfortável
- Em especial, surgem três problemas
- a lógica de validação fica duplicada entre o manipulador
onChangee a etapa de renderização inicial - o código de validação inicial fica separado do elemento de entrada, reduzindo a coesão e aumentando o risco de alterar apenas um dos lados
- a combinação de
useRef,useLayoutEffecteonChangefica excessiva à medida que o número de entradas cresce, e causa ainda mais confusão quando apenas alguns campos usamcustomValidity
- a lógica de validação fica duplicada entre o manipulador
- Essa complexidade fica especialmente evidente ao lidar com uma API imperativa pura dentro de componentes declarativos
CustomValiditynão possui um atributo de entrada que permita definir seu valor declarativamente, como ocorre com os atributos nativos de validação- Quando a API é incômoda, a adoção pode ser baixa mesmo que o recurso seja poderoso, e esse é o principal motivo da subutilização da validação nativa de formulários
A peça que falta: o atributo custom-validity
- O formato necessário seria um atributo declarativo como
custom-validity, que pudesse ser definido diretamente na entrada - Em frameworks declarativos, esse atributo permitiria manter o estado de validação próximo ao elemento de entrada
- Atualmente, a
HTML Specnão possui umcustom-validityreal - É possível simular esse comportamento com uma implementação no espaço do usuário para fins de demonstração
- Como exemplo de componente para produção, é fornecida uma implementação mais completa
Validação assíncrona e dependência entre entradas
- A validação em aplicações reais pode ser mais complexa do que verificações locais
- Um campo de nome de usuário precisa consultar o servidor para verificar se o nome já está em uso, e, durante a requisição, o formulário não deveria ser considerado válido
- O exemplo usa
requiredpara impedir entradas vazias e, conforme o estado de carregamento e o resultado da resposta, usacustomValiditypara tornar a entrada inválida - A implementação é composta por duas partes
- o estado da requisição de verificação de unicidade do nome de usuário é gerenciado com
useQuerydoreact-query - é usado um componente personalizado
<Input />que pode receber a propcustomValidity
- o estado da requisição de verificação de unicidade do nome de usuário é gerenciado com
- Essa abordagem permite expressar todo o fluxo de validação assíncrona — incluindo estados de carregamento, erro e sucesso — em um único atributo
- Um exemplo adicional mostra um formulário que exige redigitar a senha informada, tratando a validação de campos de entrada dependentes entre si
Conclusão
setCustomValidityé uma ferramenta poderosa, capaz de atender a uma ampla variedade de requisitos de validação- O que determina o uso real não é apenas a existência do recurso, mas também uma API que o torne fácil de usar
- Uma abstração declarativa como
custom-validitypermitiria usar a validação nativa de formulários de forma mais natural - É razoável esperar que algo assim entre nativamente na especificação HTML no futuro
1 comentários
Opiniões no Hacker News
Da última vez que verifiquei, os navegadores web atuais ainda não permitiam estilizar a aparência das mensagens de validação HTML embutidas https://stackoverflow.com/questions/5328883/how-do-i-style-t...
Seria menos pior se o Chrome e o Firefox pelo menos seguissem as diretrizes de UI da plataforma do SO e parecessem algo exibido pelo sistema, como um tooltip
title="", mas o Chrome usa um ícone amarelo/laranja, texto preto sobre fundo branco e um balão com cantos arredondados fixos, o que entra em forte conflito com a estética do projeto atualO Chrome antigo permitia estilizar mensagens de validação com seletores de pseudo-elementos com prefixo de fornecedor, mas removeu esse recurso e nunca o trouxe de volta. Isso acaba entrando na lista arbitrária de irritações do tipo “me deem uma combobox HTML nativa decente” e “por que isso ainda é uma caixa difícil de usar com Ctrl+clique, e não uma lista de checkboxes?”
validityda entrada e renderizar como quiserO problema real é que é complicado assinar com precisão as mudanças desse estado de validade. Existem eventos de validade, mas eles nem sempre disparam. Se você altera o estado do formulário programaticamente, como ao chamar
form.reset()ou fazerinput.value = '...', esses eventos não são disparadosAcho que esse é um bom tema para investigar separadamente e propor à plataforma web
A coisa maior, mais fácil de implementar e ainda assim subutilizada é usar valores de atributo
typeespecíficos, comoemail,numbereurlNo mobile, isso pode abrir o teclado ideal e melhorar bastante a experiência do usuário
type=numbere usotype=text inputmode=numericem vez disso. Ele não adiciona botões de seta, que a maioria dos usuários não precisa para inserir números, e o teclado no iOS também fica melhortypecorretodate. Cada biblioteca de frontend tem seu próprio widget de data, e muitos ficam péssimos em telas pequenasÉ preciso adicionar JS e CSS por causa daquele único widget, e alguns dependem de jQuery. Claro que alguns têm muitas configurações, mas por um custo muito pequeno você obtém um widget que é decente em qualquer lugar, parece nativo, em geral atende aos requisitos e pode ser esquecido sem se preocupar com atualizações ou CDN
Com entradas comuns isso mal é possível; entradas especiais dão suporte a ainda menos eventos
Meu produto não passou em uma auditoria de acessibilidade porque usava validação nativa de formulários HTML, e a recomendação oficial foi implementar uma camada própria de validação. Eu também concordo com essa recomendação
A validação nativa do HTML tem muitas falhas e, sinceramente, a customização visual nem é a maior preocupação. Mas é quase o último prego no caixão
Por exemplo, se você quiser mostrar vários erros por campo de uma vez, a única opção é concatenar strings. Algo como “É preciso um número. É preciso um símbolo. Deve ter mais de 10 caracteres”, o que é ruim para a experiência do usuário e também para acessibilidade. Isso porque não dá para navegar pela string concatenada na árvore de acessibilidade. Não é um problema de implementação, é um problema da própria especificação. Não é divertido para o usuário ficar brincando de caça aos erros de validação, então deveria ser possível mostrar vários erros de uma vez
A dependência do navegador também é ruim. Você não tem controle, e as implementações em geral são péssimas. O Chrome mostra um pop-up ao receber foco; é um pop-up, parece modal e não consegue mostrar todos os erros do formulário de uma vez, então por si só já não é bom para acessibilidade. Não mostrar informações importantes em pop-ups é um princípio básico de acessibilidade, mas o navegador não tem muito mais o que fazer sem interferir no documento real
Erros do formulário inteiro que não pertencem a um campo específico também continuam exigindo validação customizada. É o caso de erros como “os campos A e B são incompatíveis”, e, nesse caso, é melhor ter uma abordagem de validação consistente
Se você tiver uma entrada customizada que não se encaixa nos tipos nativos de input, precisa ter um input oculto por consistência, e isso também quebra a acessibilidade. Corrigir isso acaba sendo tão difícil quanto criar um sistema próprio de validação acessível
A API
customValidityé imperativa e chata de usar. A menos que você queira mensagens horríveis como “este campo é inválido”, quase não existe a opção de não usar validade customizada. Por isso a validação de formulários HTML é péssimaAinda assim, acho que vale a pena aproveitar o mecanismo nativo de validação. Você não precisa necessariamente usar o tooltip nativo de validade para mensagens de erro; pode ler diretamente o
ValidityStatedeinput.validity, renderizar as mensagens do jeito que quiser e mostrar vários erros se necessárioOs navegadores podem melhorar, e usar uma abordagem padronizada permite se beneficiar disso. Uma conclusão do tipo “se você não usar pop-up, a acessibilidade quebra” soa estranha, mas, se for preciso atender à auditoria, talvez seja o caminho
Também há técnicas para lidar com erros que não pertencem a um campo específico. Nos meus projetos, uso um componente
HiddenValidationInput, que cria um input invisível e somente leitura para renderizar erros customizados que não pertencem a outros campos. Como basta renderizá-lo condicionalmente, na prática ele é bem agradável de usarConcordo totalmente que a API
customValidityé imperativa e incômoda. Escrevi um artigo justamente sobre essa parte, e espero que isso também melhore com o tempoO design tosco e pouco acessível do Chrome é um problema do Chrome, então vou abrir um relatório de bug
Quando você fala em “especificação”, é isto aqui? https://html.spec.whatwg.org/multipage/dom.html#concept-elem...
Não vejo uma parte que defina como as mensagens de validação devem ser exibidas. Nem mesmo vejo algo dizendo que as mensagens precisam obrigatoriamente existir
Para ser sincero, parece que as pessoas que escrevem essas especificações estão desconectadas da realidade e não usam de fato aquilo que especificaram. Funciona para coisas muito simples, mas, quando os formulários começam a evoluir, você acaba percebendo que é melhor escrever tudo por conta própria
No momento em que você começa a usar JS, fica muito mais fácil fazer tudo por código do que ficar mexendo nos atributos de validação
Se um checkbox tiver um rótulo, eu gostaria que colocassem o atributo
forno rótulo para que clicar nele marque e desmarque o checkbox. Pessoalmente, é uma das coisas que mais me incomodam, mas talvez seja só comigoinputcom olabeltambém funciona. Não sei muito bem por que as pessoas tendem a separar os doisE também não sei por que os navegadores começaram a separá-los. Acho que o texto não deveria conter checkboxes e radios; os checkboxes e radios é que deveriam conter o texto
Há um exemplo simples sem React aqui
https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectE...
A validação de formulários HTML é excelente. Só que há uma armadilha enorme:
ela não funciona no Firefox para Android
https://bugzilla.mozilla.org/show_bug.cgi?id=1510450
Não vale a pena ignorar o padrão por causa de menos de 1% dos usuários que não conseguem ver a mensagem de erro dos controles marcados em vermelho. Quanto mais sites usarem isso, maior será a pressão para a Mozilla corrigir o navegador
Isso não é uma API como WebMIDI, que Chrome ou Safari implementaram antes da padronização; faz parte da especificação original do HTML5
Ainda mais considerando quão pouco tempo se gasta para garantir que pessoas que usam software de acessibilidade consigam usar os sites corretamente. A menos que você esteja pronto para falar até do UC Browser, não acho que valha muito a pena mencionar esse tipo de problema
Ainda assim, poder usar o uBlock Origin junto com outras extensões é uma vantagem poderosa
[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1884282
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=1897707
Se a validação simplesmente não tivesse sido implementada, até seria melhor; isso é uma verdadeira bagunça. Descobri isso há alguns anos, depois de uma depuração longa e dolorosa, e tenho certeza de que não fui a primeira nem a última pessoa a passar pela mesma coisa
Até pesquisar, eu não conseguia nem imaginar o que estava deixando passar, e foi bem chocante. Criar sua própria validação tudo bem, mas isto deveria funcionar
É preciso tomar cuidado para não exagerar no uso
Recentemente tentei obter um reembolso no Groupon. A empresa para a qual eu havia comprado o Groupon passou para uma nova gestão e não aceitou meu Groupon
O formulário tinha a condição de “no mínimo 15 palavras”, mas, por mais que eu tentasse, não conseguia passar na validação, então acabei tendo que inspecionar o HTML
\wera caractere de palavra,\bera limite de palavra,\sera espaço em branco, e pontuação literalmente não era permitida de forma algumapattern, são legais, mas não bastamPor exemplo, o caso de “repetir a senha” pode ser implementado com o atributo
pattern, semsetCustomValidity. Para isso, seria preciso construir dinamicamente uma expressão regular a partir do primeiro valor digitadoNão comparei as soluções para o texto não ficar longo demais, mas o ponto principal é que usar
customValiditytorna a validação muito mais clara e legível. Aqui, uma API melhor faz uma grande diferençaA restrição de “no mínimo 15 palavras” também poderia ser expressa de forma bem mais legível, como
value.split(/\s+/).length >= 15Há um motivo para ser pouco usado. Muitos frameworks e bibliotecas oferecem recursos de validação robustos e passíveis de estilização, e alguns são muito sofisticados e extensíveis. Se não é preciso sofrer, é melhor não se torturar
curlou alguma outra ferramenta que não tenha a mesma validação de formulário embutidaNão dá para confiar que o cliente fez a validação, nem que a fez corretamente, então o backend ainda precisa validar a entrada e ser capaz de exibir o formulário com os erros de validação de todos os campos
A validação no frontend serve apenas para ajudar o usuário. Mas, se você quiser estilizar ou disparar a partir do backend no momento do envio, acaba tendo que implementar sua própria estilização de qualquer jeito
Há armadilhas demais e, para dar suporte fácil a verificações mais complexas, você acaba usando uma biblioteca
Além disso, ao usar uma biblioteca, dependendo do caso, também surge a possibilidade de compartilhar parte do código de validação entre frontend e backend
Em especial, este artigo parece contornar um problema com
useLayoutEffect, o que não é algo para se fazer de forma levianaUma das coisas de que não gosto na validação de formulários HTML é que ela roda desde o carregamento da página. Por exemplo, se você vincular estilos de estado de erro a isso, o formulário pode carregar já cheio de erros desde o início, parecendo intimidador para o usuário
:user-invalid, que ajuda a evitar isso até certo ponto. Mas ela tem partes pouco flexíveis, então pode não ser suficiente dependendo do caso de usohttps://developer.mozilla.org/en-US/docs/Web/CSS/:user-inval...
Dá para contornar com script, mas, nesse ponto, esse recurso já não faz muita coisa por você. Acho que esse é o maior motivo para ele não ser adotado mais amplamente