Skip to content
This page has been auto-translated and may contain errors.View in English

Validação de formulários ​

docs.scrimba.com

Um formulário coleta dados de pessoas, e pessoas deixam campos em branco, digitam um e-mail errado ou colocam letras onde deveria haver um número. Você não quer que esses dados cheguem ao seu código sem qualquer verificação. Antes de qualquer envio, o navegador consegue checar boa parte disso para você, mas só se você descrever o que cada campo espera receber. Essa descrição é escrita direto no seu HTML, e este capítulo é sobre como escrevê-la.

Campos obrigatórios e tipos de entrada ​

As duas verificações mais baratas que você pode adicionar são dizer que um campo precisa ser preenchido e dizer que tipo de valor deve ir nele.

Adicione a palavra required a um input e o navegador se recusa a enviar o formulário até que esse campo tenha algo preenchido. Você não escreve nenhum código para isso funcionar. O atributo já é a instrução.

html
<input type="text" name="full_name" required>

A outra parte é o atributo type. Ele diz ao navegador que formato o valor deve ter, e o navegador verifica isso para você. Use type="email" e o navegador garante que o valor se pareça com um endereço de e-mail. Use type="number" e ele só aceita números. Existe um tipo para datas, um para endereços da web, e vários outros.

Pense em um formulário de consultório médico em que algumas caixas estão marcadas como "obrigatório preencher" e a caixa de data já vem com espacinhos separados para dia, mês e ano. O papel avisa o que vai em cada lugar antes mesmo de você escrever qualquer coisa.

required é um atributo booleano: só a presença dele já ativa a regra, e ele não recebe valor nenhum. Um campo marcado com required bloqueia o envio do formulário enquanto estiver vazio.

O atributo type faz um trabalho duplo. Ele escolhe o controle que o navegador renderiza (um seletor de data, um contador numérico) e define uma restrição embutida sobre o valor:

  • type="email" exige um @ e uma parte final com formato de domínio.
  • type="url" exige algo que seja interpretado como um endereço da web absoluto.
  • type="number" aceita só entrada numérica e libera min, max e step.
  • type="tel" não restringe o valor, mas abre um teclado numérico de telefone no celular.

Escolha o tipo mais específico antes de recorrer a qualquer outra coisa. É a menor quantidade de código para a maior verificação possível, e ainda melhora o teclado que aparece na tela em dispositivos touch.

required e type são as duas primeiras camadas do que a plataforma chama de validação de restrições: o sistema embutido do navegador para checar o valor de um campo contra regras que você declara no markup. required define a regra de "valor ausente". type define uma regra de formato ligada a esse tipo.

Duas observações práticas sobre type. Primeiro, type="email" não valida contra a especificação completa de e-mail. Os navegadores usam um padrão deliberadamente permissivo (uma sequência de caracteres, um @, depois um domínio separado por pontos) porque uma verificação mais estrita rejeitaria endereços que na prática são entregáveis. Trate isso como uma checagem de formato, não como prova de que o endereço existe. Segundo, um type desconhecido ou não suportado volta para type="text", então um campo nunca fica inutilizável em um navegador mais antigo, ele só perde a verificação extra. Essa degradação suave é o motivo pelo qual você pode adotar tipos mais novos sem precisar consultar uma tabela de compatibilidade.

O tipo também determina o teclado exibido na tela e, com o atributo relacionado inputmode (uma dica sobre qual layout de teclado mostrar, sem alterar a validação), você pode ajustar esse teclado de forma independente. Escolher o tipo certo é tanto uma decisão de acessibilidade e ergonomia quanto uma decisão de validação.

Aqui está um pequeno formulário de cadastro usando os dois atributos:

html
<form>
  <label>
    Endereço de e-mail
    <input type="email" name="email" required>
  </label>
  <label>
    Site
    <input type="url" name="website">
  </label>
  <button>Cadastrar</button>
</form>

O campo de e-mail precisa ser preenchido e precisa se parecer com um e-mail. O campo do site é opcional, mas, se alguém digitar algo, precisa se parecer com um endereço da web. O navegador verifica os dois no momento em que o botão é pressionado.

JunoCampos obrigatórios e tipos de entrada Duas palavrinhas fazem muito trabalho aqui. Coloque required em um campo e o navegador não deixa passar um campo vazio. Defina o type, como email ou number, e ele verifica se o valor tem o formato certo. Escolha o tipo que combina com o campo e você ganha a verificação de graça.
JunoCampos obrigatórios e tipos de entradarequired é um atributo booleano: ou ele está presente, ou não está, sem precisar de valor. O type escolhe o controle e também define uma regra de formato, então type="email" exige um @ e um domínio. Comece sempre pelo tipo mais específico; é o markup mais econômico para a maior verificação, e o teclado do celular também melhora.
JunoCampos obrigatórios e tipos de entrada Essas são as duas primeiras camadas da validação de restrições, o verificador de regras embutido no navegador. Vale lembrar: type="email" é uma checagem de formato solta e não é prova de que o endereço é real, e um tipo não suportado volta discretamente para text em vez de quebrar. Escolher o tipo também é uma decisão de acessibilidade, já que define o teclado exibido na tela.

Atributos de restrição ​

Além de "preenchido" e "tipo certo", muitas vezes você quer limites: um valor com no mínimo tal tamanho, um número não maior que tal valor, um código em um formato específico. Um punhado de atributos cobre isso.

Você pode definir limites para o que um campo aceita. Dois úteis para começar:

html
<input type="text" name="username" maxlength="20">
<input type="number" name="quantity" min="1" max="10">

maxlength="20" impede que o campo passe de vinte caracteres. min e max definem o menor e o maior número permitido. O navegador mantém as pessoas dentro desses limites, então você não precisa verificar isso sozinho.

Existe um pequeno conjunto de atributos de restrição, e cada um corresponde a uma regra:

  • minlength / maxlength definem o número mínimo e máximo de caracteres que um valor de texto pode ter.
  • min / max definem o menor e o maior valor para números e datas.
  • step define o incremento permitido para um número, então step="5" aceita 0, 5, 10, e rejeita 3.
  • pattern contém uma expressão regular (uma notação compacta para descrever o formato exato de um texto permitido) que o valor precisa corresponder por completo.
html
<input type="text" name="username" minlength="3" maxlength="20" required>
<input type="number" name="quantity" min="1" max="10" step="1">
<input type="text" name="pin" pattern="[0-9]{4}" title="Quatro dígitos">

Combine pattern com um title: alguns navegadores mostram esse texto na mensagem de erro, e ele informa aos usuários que conseguem ver a tela o que o campo espera. No CSS, :valid e :invalid permitem estilizar um campo de acordo com se ele atende ou não às restrições atualmente.

Cada atributo de restrição corresponde a um sinalizador que o navegador pode levantar sobre um campo, o que importa quando você lê o estado de validação a partir do JavaScript. Violar min levanta "range underflow" (valor abaixo do mínimo), exceder maxlength levanta "too long" (muito longo), não corresponder a pattern levanta "pattern mismatch" (padrão incompatível), e assim por diante. O atributo é a declaração; o sinalizador é como o resultado é reportado.

Dois detalhes que costumam confundir. pattern é implicitamente ancorado: a expressão precisa corresponder ao valor inteiro, como se estivesse envolvida para cobrir do início ao fim, então pattern="[0-9]{4}" significa exatamente quatro dígitos, não "contém quatro dígitos". E step é medido a partir de uma base (o min, ou zero se não houver nenhum) usando aritmética de ponto flutuante, então step="0.1" pode rejeitar um valor que você esperava que passasse por causa de como o formato binário representa números decimais; quando a precisão importa, defina um min explícito como base.

As pseudoclasses :valid e :invalid (seletores CSS que correspondem a um elemento com base em seu estado, não em sua posição) são úteis, mas pouco refinadas: um campo required vazio corresponde a :invalid desde a primeira renderização, então estilizá-lo em vermelho recebe o usuário com erros antes mesmo de ele ter digitado alguma coisa. A solução é :user-invalid, que só corresponde depois que a pessoa interagiu com o campo, para que o feedback apareça quando é realmente útil, e não logo de cara.

A regra que evita mais confusão do que qualquer outra: um pattern precisa corresponder ao valor inteiro, não só a uma parte dele.

html
<input
  type="text"
  name="product_code"
  pattern="[A-Z]{2}-[0-9]{3}"
  title="Duas letras, um hífen, três dígitos, por exemplo AB-123"
  required
>
JunoAtributos de restrição Depois que um campo é obrigatório e tem um tipo definido, você pode adicionar limites. maxlength limita quantos caracteres entram, e min e max cercam um número. O navegador mantém as pessoas dentro desses limites automaticamente, o que representa muita verificação que você nunca precisa escrever.
JunoAtributos de restrição O conjunto é pequeno: minlength/maxlength para texto, min/max para números e datas, step para incrementos, e pattern para um formato exato. Dê ao pattern um title para que a mensagem tenha sentido. E pattern corresponde ao valor inteiro, então [0-9]{4} são quatro dígitos no total, não quatro dígitos em algum lugar no meio.
JunoAtributos de restrição Cada atributo corresponde a um sinalizador de validade que você pode ler depois, então o markup e o relato ficam alinhados. Duas pegadinhas: pattern é ancorado do início ao fim, e step usa matemática de ponto flutuante a partir de uma base, então defina um min explícito quando a precisão importar. Estilize com :user-invalid, não com :invalid, ou você vai pintar de vermelho um campo que ninguém tocou antes mesmo de alguém digitar algo.

Feedback nativo de validação ​

Declarar as regras é só metade da história. A outra metade é o que a pessoa vê quando um valor infringe uma delas.

Quando alguém pressiona o botão de envio e um campo está errado, o navegador interrompe o envio e mostra uma pequena mensagem ao lado do primeiro campo com problema, e leva o cursor até ele. Você não precisa criar essa mensagem. O navegador a escreve, no idioma do próprio visitante, e a mostra automaticamente.

Assim, um campo de e-mail required deixado em branco produz algo como "Preencha este campo", e o formulário não é enviado até que isso seja corrigido.

A validação dispara no envio. O navegador percorre os campos em ordem, encontra o primeiro inválido, coloca o foco nele e mostra um pequeno balão de mensagem descrevendo o problema. Se tudo estiver correto, o formulário é enviado normalmente.

Você tem algum controle de estilo através do CSS. :required, :valid, :invalid e :in-range permitem marcar campos visualmente. O que você não consegue estilizar facilmente é o balão de mensagem em si, sua aparência pertence ao navegador. Você também pode desativar o sistema inteiro para um formulário com o atributo novalidate, o que é útil quando você pretende validar tudo em JavaScript.

css
input:user-invalid {
  border-color: #c0392b;
}
input:user-valid {
  border-color: #2d7a3f;
}

O feedback nativo é conveniente e quase gratuito, e vem com limitações que vale a pena considerar antes de confiar nele em um produto em produção.

O balão de mensagem é um elemento transitório: ele aparece no envio, desaparece na próxima interação e não faz parte do documento que você pode selecionar ou estilizar. O suporte a leitores de tela para ele é inconsistente entre navegadores, então um balão isolado não é uma forma confiável de anunciar um erro para alguém que não consegue vê-lo. O texto também é localizado de acordo com o idioma do navegador, não com o lang da sua página, então um formulário escrito em inglês pode mostrar uma mensagem de erro em francês para um visitante cujo navegador está configurado em francês. Isso é correto para o usuário e surpreendente para o desenvolvedor.

Você mesmo pode disparar o mesmo fluxo sem enviar o formulário: reportValidity() executa as verificações e mostra os balões sob demanda. Mas, para qualquer coisa além de um prototipo rápido, o padrão acessível é suprimir o balão nativo (com novalidate) e renderizar seu próprio texto de erro na página, associado ao campo, para que fique visível, estilizável e anunciado de forma confiável. A próxima seção mostra como fazer isso.

JunoFeedback nativo de validação Aqui está o retorno de todos esses atributos: aperte enviar com um campo inválido e o navegador exibe uma mensagenzinha, aponta para o campo e recusa o envio. Você não escreveu nenhum desse texto; o navegador fez isso, no idioma do visitante. Para muitos formulários, esse é todo o feedback que você precisa.
JunoFeedback nativo de validação A verificação acontece no envio: o primeiro campo inválido recebe foco e um balão de mensagem, e o formulário fica bloqueado até passar. Você pode estilizar os campos com :user-valid e :user-invalid, mas o balão em si é do navegador para desenhar. Desative tudo com novalidate quando planejar tratar isso em JavaScript.
JunoFeedback nativo de validação O balão é transitório e não pode ser estilizado, o suporte a leitores de tela é irregular, e o texto segue o idioma do navegador, não o da sua página. Está ótimo para um protótipo, mas é frágil para produção. Quando a acessibilidade importa, adicione novalidate e renderize seu próprio texto de erro na página, para que ele possa ser visto, estilizado e anunciado corretamente.

Quando você ainda precisa de JavaScript ​

A validação nativa verifica um campo contra suas próprias regras. Muitas verificações reais não se encaixam nesse formato, e é aí que o JavaScript entra em cena.

Há coisas que o navegador não consegue verificar por conta própria. Se dois campos de senha coincidem entre si. Se um nome de usuário já foi usado por outra pessoa. Se um código de desconto é real. Nenhuma dessas situações depende só do formato de um campo, então não existe atributo para elas, e você recorre ao JavaScript para fazer essa verificação.

Isso não é uma falha do HTML. As verificações embutidas cuidam dos casos comuns sem nenhum código, e o JavaScript cuida do resto.

A lacuna é qualquer coisa que depende de mais de um campo, ou de uma informação que a página ainda não tem. Um exemplo comum é confirmar se duas senhas coincidem. Você as compara em JavaScript e devolve o resultado para o sistema nativo com setCustomValidity, que define uma mensagem de erro personalizada em um campo (uma string vazia significa "isto é válido"):

html
<input type="password" id="password" name="password" required>
<input type="password" id="confirm" name="confirm" required>

A comparação em si acontece em um <script>:

js
const password = document.querySelector("#password")
const confirm = document.querySelector("#confirm")

confirm.addEventListener("input", () => {
  // string vazia limpa o erro e marca o campo como válido
  const message = confirm.value === password.value ? "" : "As senhas não coincidem"
  confirm.setCustomValidity(message)
})

Agora o campo participa da validação nativa como qualquer outro, bloqueando o envio e mostrando sua mensagem quando as senhas são diferentes. Verificações que precisam de um servidor, como disponibilidade de nome de usuário, seguem o mesmo formato, mas definem a mensagem depois que a resposta da rede chega.

O JavaScript alcança a validação nativa através da Constraint Validation API (o conjunto de propriedades e métodos que o navegador expõe nos campos de formulário para ler e definir a validade). As peças que você mais usa:

  • setCustomValidity(message) define uma string de erro personalizada em um campo. Uma string não vazia o marca como inválido e se torna sua mensagem; uma string vazia limpa o erro personalizado.
  • validity é um objeto ValidityState somente leitura: um booleano para cada possível falha (valueMissing, typeMismatch, patternMismatch, rangeOverflow, tooLong, customError, e assim por diante), além de valid para o resultado geral. Leia-o para descobrir por que um campo falhou, não só que ele falhou.
  • checkValidity() retorna verdadeiro ou falso sem mostrar nada; reportValidity() faz o mesmo e exibe os balões nativos.

Para erros acessíveis, a cor nunca é suficiente por si só: uma borda vermelha não diz nada para um leitor de tela ou para alguém que não consegue distinguir vermelho de verde. Associe o texto do erro ao campo correspondente para que a tecnologia assistiva os leia juntos. Defina aria-describedby no input apontando para o id do elemento de erro (isso diz ao leitor de tela "leia este texto como a descrição do campo"), e defina aria-invalid="true" enquanto o campo estiver inválido (isso anuncia o campo como estando em estado de erro):

html
<input
  type="text"
  id="username"
  name="username"
  aria-describedby="username-error"
  aria-invalid="true"
  required
>
<p id="username-error" role="alert">Esse nome de usuário já está em uso.</p>

O role="alert" faz um leitor de tela anunciar a mensagem no momento em que ela aparece. O capítulo de Acessibilidade entra em mais detalhes sobre como associar campos às suas descrições.

Isso nos leva à troca envolvida. A validação nativa exige menos código, é consistente com a plataforma e funciona antes mesmo de o seu script carregar, mas seu feedback é difícil de estilizar e anunciar. A validação personalizada exige mais código e mais responsabilidade, mas te dá controle total sobre o texto, o momento e a acessibilidade. A maioria dos formulários em produção usa os dois: atributos nativos como base, com JavaScript sobreposto para as verificações e os avisos que os atributos não conseguem alcançar.

Seja qual for a sua escolha, uma regra não muda. A validação no cliente é uma conveniência, nunca uma garantia. Tudo o que foi descrito neste capítulo é executado no navegador do visitante, onde qualquer pessoa pode desativá-la, editar a página ou enviar uma requisição direto para o seu servidor sem nem tocar no formulário. O servidor precisa revalidar cada valor que recebe como se nenhuma verificação do navegador tivesse acontecido. Trate as verificações do navegador como uma primeira passada rápida e amigável que evita uma ida e volta desnecessária para o usuário, e trate o servidor como a verificação que de fato protege os seus dados.

JunoQuando você ainda precisa de JavaScript As verificações embutidas cobrem um campo por vez, então qualquer coisa que compare campos, como "essas duas senhas coincidem", ou que precise checar com um servidor, como "esse nome de usuário está disponível", exige JavaScript. Isso é normal. O HTML cuida dos casos do dia a dia de graça, e o JavaScript assume os casos que ele não consegue ver por conta própria.
JunoQuando você ainda precisa de JavaScript Quando uma verificação envolve dois campos ou precisa do servidor, calcule o resultado em JavaScript e devolva-o com setCustomValidity: uma string de mensagem para invalidar o campo, uma string vazia para limpar. O campo então passa a participar da validação nativa como qualquer outro. Coincidência de senhas e disponibilidade de nome de usuário são os dois exemplos clássicos.
JunoQuando você ainda precisa de JavaScript A Constraint Validation API é o seu ponto de entrada: setCustomValidity para definir uma mensagem, validity para ler por que um campo falhou, checkValidity e reportValidity para executar as verificações. Para erros acessíveis, associe o texto ao campo com aria-describedby e aria-invalid, nunca só com cor. E o ponto que não é opcional: toda verificação daqui é executada no navegador, então o servidor precisa revalidar tudo de novo.