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 entrada de pessoas, e pessoas deixam campos em branco, digitam incorretamente um e-mail ou colocam letras onde um número deveria estar. Você não quer que esses dados cheguem ao seu código sem verificação. Antes de qualquer coisa ser enviada, o navegador pode verificar muito disso para você, mas apenas se você descrever o que cada campo espera. Essa descrição é escrita diretamente no seu HTML, e este capítulo é sobre como escrevê-la.

Campos obrigatórios e tipos de entrada

As duas verificações mais simples que você pode adicionar são dizer que um campo deve ser preenchido e dizer que tipo de valor pertence a ele.

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

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

A outra metade é o atributo type. Ele diz ao navegador qual deve ser a forma do valor, e o navegador verifica isso para você. Use type="email" e o navegador garante que o valor pareça um endereço de e-mail. Use type="number" e ele aceita apenas números. Existe um tipo para datas, um para endereços da web e mais.

Pense nisso como um formulário em um consultório médico onde algumas caixas são marcadas como "obrigatório preencher" e a caixa de data já possui pequenos espaços para dia, mês e ano. O papel está dizendo a você o que vai aonde antes de você escrever qualquer coisa.

required é um atributo booleano: sua presença sozinha ativa a regra e não recebe um valor. Um controle marcado como required bloqueia o envio do formulário enquanto estiver vazio.

O atributo type faz duplo trabalho. Ele escolhe o controle que o navegador renderiza (um seletor de data, um spinner de número) e define uma restrição integrada no valor:

  • type="email" requer um @ e um final em forma de domínio.
  • type="url" requer algo que seja analisado como um endereço web absoluto.
  • type="number" aceita apenas entrada numérica e desbloqueia min, max e step.
  • type="tel" não restringe o valor, mas traz um teclado de telefone em dispositivos móveis.

Procure pelo tipo específico antes de procurar por qualquer outra coisa. É o código mínimo para o máximo de verificação, e melhora o teclado na tela que as pessoas recebem em dispositivos sensíveis ao toque.

required e type são as duas primeiras camadas do que a plataforma chama de constraint validation: o sistema integrado do navegador para verificar o valor de um controle em relação às regras que você declara na marcação. required define a regra "valor ausente". type define uma regra de formato vinculada a esse tipo.

Duas observações de produção sobre type. Primeiro, type="email" não valida em relação à 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 rigorosa rejeitaria endereços que realmente são entregáveis. Trate-o como uma verificação de forma, não como prova de que o endereço existe. Segundo, um type desconhecido ou não suportado retorna para type="text", então um campo nunca fica inutilizável em um navegador mais antigo, apenas perde a verificação extra. Esse retorno gracioso é por isso que você pode adotar tipos mais novos sem uma tabela de compatibilidade à mão.

O tipo também orienta o teclado na tela e, através do atributo inputmode relacionado (uma dica para qual layout de teclado mostrar sem alterar a validação), você pode ajustar esse teclado independentemente. Acertar o tipo é uma decisão de acessibilidade e ergonomia tanto quanto uma de validação.

Aqui está um pequeno formulário de inscrição usando ambos os 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>Inscrever-se</button>
</form>

O campo de e-mail deve ser preenchido e deve parecer um e-mail. O campo de site é opcional, mas se alguém digitar qualquer coisa, deve parecer um endereço da web. O navegador verifica ambos no momento em que o botão é pressionado.

JunoCampos obrigatórios e tipos de entrada Duas pequenas palavras fazem muito trabalho aqui. Coloque required em um campo e o navegador não deixará um vazio passar. Defina o type, como email ou number, e ele verifica se o valor tem a forma correta. Escolha o tipo que corresponde ao campo e você recebe verificação gratuitamente.
JunoCampos obrigatórios e tipos de entradarequired é um atributo booleano: ele está lá ou não, sem valor necessário. O type escolhe o controle e define uma regra de formato, então type="email" quer um @ e um domínio. Sempre comece com o tipo mais específico; é a marcação mínima para o máximo de verificação, e os teclados móveis também ficam melhores.
JunoCampos obrigatórios e tipos de entrada Estas são as duas primeiras camadas de constraint validation, o verificador de regras integrado do navegador. Vale lembrar: type="email" é uma verificação de forma aberta, não prova de que o endereço é real, e um tipo não suportado volta silenciosamente para text em vez de quebrar. Escolher o tipo é também uma questão de acessibilidade, já que define o teclado na tela que as pessoas recebem.

Atributos de restrição

Além de "preenchido" e "tipo correto", frequentemente você quer limites: um valor com pelo menos este comprimento, um número não maior que aquele, um código em um formato específico. Um punhado de atributos cobre isso.

Você pode definir limites sobre 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" para o campo uma vez que contém vinte caracteres. min e max definem o menor e o maior número permitido. O navegador mantém as pessoas dentro desses limites para que você não tenha que verificar você mesmo.

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

  • minlength / maxlength definem o mínimo e o máximo de caracteres que um valor de texto pode ter.
  • min / max definem o valor mais baixo e mais alto 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 a forma exata do texto permitido) que o valor deve corresponder completamente.
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 title: alguns navegadores mostram seu texto na mensagem de erro, e isso diz aos usuários com visão o que o campo espera. Em CSS, :valid e :invalid permitem que você estilize um controle com base em se ele atualmente passa em suas restrições.

Cada atributo de restrição corresponde a uma flag que o navegador pode levantar sobre um controle, o que é importante uma vez que você lê o estado de validação a partir do JavaScript. min violado levanta "range underflow", maxlength excedido levanta "too long", pattern não correspondido levanta "pattern mismatch", e assim por diante. O atributo é a declaração; a flag é como o resultado é relatado.

Dois detalhes que confundem as pessoas. pattern é implicitamente ancorado: a expressão deve corresponder ao valor inteiro, como se fosse 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ê espera passar por causa de como o binário representa decimais; quando a precisão importa, defina um min explícito como a base.

As pseudo-classes :valid e :invalid (seletores CSS que correspondem a um elemento com base em seu estado em vez de sua posição) são úteis, mas imprecisas: um campo required vazio corresponde a :invalid desde a primeira pintura, então estilizá-lo em vermelho cumprimente o usuário com erros antes de ele ter digitado qualquer coisa. A solução é :user-invalid, que só corresponde depois que a pessoa interagiu com o campo, então o feedback chega quando é útil em vez de na chegada.

A regra que poupa a maioria das confusões: um pattern deve corresponder ao valor inteiro, não apenas parte dele.

html
<input
  type="text"
  name="product_code"
  pattern="[A-Z]{2}-[0-9]{3}"
  title="Duas letras, hífen, três dígitos, ex. AB-123"
  required
>
JunoAtributos de restrição Depois que um campo é obrigatório e tipado, você pode adicionar limites. maxlength limita quantos caracteres entram, e min e max delimitam um número. O navegador mantém as pessoas dentro desses limites automaticamente, o que é muito verificação que você nunca tem que 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 uma forma exata. Dê a pattern um title para que a mensagem signifique algo. E pattern corresponde ao valor inteiro, então [0-9]{4} é quatro dígitos totais, não quatro dígitos em algum lugar dentro.
JunoAtributos de restrição Cada atributo mapeia para uma flag de validade que você pode ler depois, então a marcação e o relatório se alinham. Dois problemas: pattern é ancorado de ponta a ponta, e step usa matemática de ponto flutuante a partir de uma base, então defina um min explícito quando a precisão contar. Estilize com :user-invalid, não :invalid, ou você pintará um campo intocado em vermelho antes de alguém digitar.

Feedback de validação nativa

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

Quando alguém pressiona o botão de envio e um campo está errado, o navegador para o envio e mostra uma pequena mensagem ao lado do primeiro campo com um problema, depois move o cursor para ele. Você não cria essa mensagem. O navegador a escreve, no idioma do visitante, e a mostra automaticamente.

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

A validação dispara no envio. O navegador percorre os controles em ordem, encontra o primeiro inválido, o foca e mostra uma pequena bolha de mensagem descrevendo o problema. Se tudo passar, o formulário é enviado normalmente.

Você obtém um controle de estilo através do CSS. :required, :valid, :invalid e :in-range permitem que você marque campos visualmente. O que você não pode facilmente restylar é a bolha de mensagem em si, sua aparência é do navegador, não sua. Você também pode desligar todo o sistema para um formulário com o atributo novalidate, o que é útil quando você pretende validar inteiramente 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 limites que vale a pena planejar antes de confiar nele em um produto enviado.

A bolha de mensagem é um elemento transitório: 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 isso é desigual entre navegadores, então uma bolha sozinha não é uma maneira confiável de anunciar um erro a alguém que não consegue vê-la. Seu texto também é localizado para o idioma do navegador, não para o lang da sua página, então um formulário escrito em inglês pode mostrar uma mensagem de erro em português para um visitante cujo navegador está configurado para português. Isso é correto para o usuário e surpreendente para o desenvolvedor.

Você pode desencadear o mesmo fluxo você mesmo sem enviar: reportValidity() executa as verificações e mostra as bolhas sob demanda. Mas para qualquer coisa além de um protótipo rápido, o padrão acessível é suprimir a bolha nativa (com novalidate) e renderizar seu próprio texto de erro na página, vinculado ao campo, para que seja visível, estilizável e anunciado de forma confiável. A próxima seção cobre como.

JunoFeedback de validação nativa Aqui está a recompensa por todos esses atributos: pressione envio com um campo ruim e o navegador abre uma pequena mensagem, aponta para o campo e recusa enviar. Você não escreveu nenhum desse texto; o navegador fez, no idioma do visitante. Para muitos formulários, este é todo o feedback que você precisa.
JunoFeedback de validação nativa A verificação é executada no envio: o primeiro campo ruim recebe foco e uma bolha de mensagem, e o formulário é retido até passar. Você pode estilizar os campos com :user-valid e :user-invalid, mas a bolha em si é do navegador para projetar. Desligue tudo com novalidate quando planejar lidar com isso em JavaScript.
JunoFeedback de validação nativa A bolha é transitória e não estilizável, seu suporte a leitor de tela é limitado, e seu texto segue o idioma do navegador, não o da sua página. Bom para um protótipo, insuficiente para produção. Quando acessibilidade importa, adicione novalidate e renderize seu próprio texto de erro na página para que possa ser visto, estilizado e anunciado adequadamente.

Quando você ainda precisa de JavaScript

A validação nativa verifica um campo em relação às suas próprias regras. Muitas verificações reais não se encaixam nessa forma, e é aí que JavaScript entra.

Algumas coisas o navegador não consegue verificar por conta própria. Se dois campos de senha correspondem um ao outro. Se um nome de usuário já foi escolhido por outra pessoa. Se um código de desconto é real. Nenhuma dessas é sobre a forma de um campo, então não há atributo para isso, e você recorre ao JavaScript para fazer a verificação.

Isso não é uma falha do HTML. As verificações integradas lidam com os casos comuns sem nenhum código, e o JavaScript lida com o resto.

A lacuna é qualquer coisa que dependa de mais de um campo ou de informações que a página ainda não possui. Um exemplo comum é confirmar que duas senhas correspondem. Você as compara em JavaScript e alimenta o resultado de volta ao sistema nativo com setCustomValidity, que define uma mensagem de erro personalizada em um controle (uma string vazia significa "isso é válido"):

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

A comparação em si é executada 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 correspondem"
  confirm.setCustomValidity(message)
})

O campo agora participa da validação nativa como qualquer outro, bloqueando o envio e mostrando sua mensagem quando as senhas diferem. Verificações que precisam de um servidor, como disponibilidade de nome de usuário, seguem a mesma forma, mas definem a mensagem após uma solicitação de rede voltar.

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

  • setCustomValidity(message) define uma string de erro personalizado em um controle. 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 por possível falha (valueMissing, typeMismatch, patternMismatch, rangeOverflow, tooLong, customError e outros), mais valid para o resultado geral. Leia-o para descobrir por que um campo falhou, não apenas que falhou.
  • checkValidity() retorna verdadeiro ou falso sem mostrar nada; reportValidity() faz o mesmo e exibe as bolhas nativas.

Para erros acessíveis, cor nunca é suficiente por si só: uma borda vermelha não diz nada a um leitor de tela ou a alguém que não consegue distinguir vermelho de verde. Associe o texto de erro ao seu campo para que a tecnologia assistiva os leia juntos. Defina aria-describedby no input para o id do elemento de erro (isso diz a um leitor de tela "leia este texto como a descrição do campo") e defina aria-invalid="true" enquanto o campo está falhando (isso anuncia o campo como estando em um 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á foi escolhido.</p>

O role="alert" faz um leitor de tela anunciar a mensagem assim que aparece. O capítulo Accessibility aprofunda-se em associar controles às suas descrições.

Isso deixa a compensação. Validação nativa é menos código, é consistente com a plataforma e funciona antes de seu script carregar, mas seu feedback é difícil de estilizar e anunciar. Validação personalizada é mais código e mais responsabilidade, mas oferece controle total sobre redação, cronograma e atributos de acessibilidade. A maioria dos formulários de produção usa ambos: atributos nativos como linha de base, JavaScript em camadas para as verificações e mensagens que atributos não conseguem alcançar.

Seja qual for sua escolha, uma regra não se curva. Validação do cliente é uma conveniência, nunca uma garantia. Tudo descrito neste capítulo é executado no navegador do visitante, onde qualquer pessoa pode desligar, editar a página ou enviar uma solicitação diretamente ao seu servidor sem tocar no formulário. O servidor deve revalidar cada valor que recebe como se nenhuma verificação de navegador tivesse acontecido. Trate as verificações do navegador como um primeiro passe rápido e amigável que poupa seus usuários de uma rodada, e trate o servidor como a verificação que realmente protege seus dados.

JunoQuando você ainda precisa de JavaScript As verificações integradas cobrem um campo por vez, então qualquer coisa comparando campos, como "estas duas senhas correspondem", ou verificando com um servidor, como "este nome de usuário é livre", precisa de JavaScript. Isso é normal. HTML lida com os casos cotidianos gratuitamente, e JavaScript pega os que ele não consegue ver por conta própria.
JunoQuando você ainda precisa de JavaScript Quando uma verificação abrange dois campos ou precisa do servidor, calcule-a em JavaScript e devolva o resultado com setCustomValidity: uma string de mensagem para falhar no campo, uma string vazia para limpá-la. O campo então se junta à validação nativa como qualquer outro. Correspondência de senha e disponibilidade de nome de usuário são os dois clássicos.
JunoQuando você ainda precisa de JavaScript A Constraint Validation API é seu gancho: 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, vincule o texto ao campo com aria-describedby e aria-invalid, nunca apenas cor. E o que não é opcional: cada verificação aqui é executada no navegador, então o servidor tem que revalidar tudo isso.