Forms

O navegador já sabe fazer boa parte do trabalho de um formulário. Ele valida o formato do e-mail, abre o teclado numérico no celular, preenche o endereço que o usuário salvou e mostra a mensagem de campo obrigatório. Tudo isso vem dos atributos da marcação, sem uma linha de JavaScript.

Esta página mostra como pedir esse trabalho ao navegador: amarrar cada label (rótulo) ao seu input (campo), agrupar campos relacionados num fieldset (conjunto de campos) e escolher o type certo para cada dado.

Conceitos fundamentais

ConceitoO que é
form (formulário)O elemento que agrupa os campos e dispara o envio, configurado por action e method
input (campo)O elemento de entrada. O type dele decide o teclado do celular e a validação
label (rótulo)O texto do campo, ligado a ele por for e id. Vira área de clique e é o que o leitor de tela anuncia
fieldset (conjunto de campos)Agrupa campos relacionados, com a <legend> nomeando o grupo
autocomplete (autopreenchimento)Diz ao navegador que dado o campo espera, como email ou tel, para ele oferecer o que o usuário já salvou
client-side validation (validação no navegador)required, type, pattern, min e max, conferidos antes do envio
placeholder (texto de exemplo)A dica dentro do campo, que some assim que o usuário digita

Todo campo tem uma <label> própria

Ligue a <label> ao campo pelo par for e id. Isso dá duas coisas ao usuário: o leitor de tela anuncia o nome do campo ao chegar nele, e um clique no texto do rótulo põe o cursor dentro do campo, o que amplia bastante a área de acerto no celular.

O placeholder não faz esse papel. Ele some assim que a pessoa começa a digitar, e é justamente aí que ela precisa conferir o que aquele campo pedia. Em formulário longo, o usuário apaga o que escreveu só para reler a dica. Some a isso o contraste baixo do texto de exemplo na maioria dos temas.

O placeholder continua útil ao lado do rótulo, para mostrar o formato esperado: name@example.com.

❌ Ruim: nenhum rótulo, e o texto de exemplo some ao digitar
<input type="text" placeholder="First name" />
<input type="email" placeholder="Email address" />
<input type="password" placeholder="Password" />
✅ Bom: rótulo ligado ao campo por for e id, com o exemplo de formato ao lado
<label for="first-name">First name</label>
<input id="first-name" type="text" name="first_name" autocomplete="given-name" />

<label for="email">Email address</label>
<input id="email" type="email" name="email" placeholder="name@example.com" autocomplete="email" />

<label for="password">Password</label>
<input id="password" type="password" name="password" autocomplete="new-password" />

Grupo de opções vai dentro de <fieldset> com <legend>

Um grupo de radio ou de checkbox precisa de uma pergunta, e a <legend> é onde ela mora. Sem o <fieldset>, o leitor de tela anuncia "Standard, botão de opção" e não diz do que se trata: a pergunta "Shipping method" está num <p> solto acima, que não pertence ao grupo.

Com o par <fieldset> e <legend>, o leitor de tela anuncia a pergunta antes de cada opção, e o usuário entende o que está escolhendo.

Vale também para blocos maiores do formulário, como separar os dados de entrega dos dados de pagamento.

❌ Ruim: as opções soltas, e a pergunta num parágrafo que não pertence ao grupo
<p>Shipping method</p>
<input id="standard" type="radio" name="shipping" value="standard" />
<label for="standard">Standard</label>
<input id="express" type="radio" name="shipping" value="express" />
<label for="express">Express</label>
✅ Bom: a legend faz a pergunta, e o leitor de tela anuncia ela antes de cada opção
<fieldset>
  <legend>Shipping method</legend>

  <label>
    <input type="radio" name="shipping" value="standard" />
    Standard (5–7 business days)
  </label>

  <label>
    <input type="radio" name="shipping" value="express" />
    Express (1–2 business days)
  </label>
</fieldset>

O type do campo decide o que o navegador entrega

type="text" em tudo funciona, e joga fora o que o navegador daria de graça. Trocar pelo tipo certo muda a experiência no celular, que é onde a maioria dos formulários é preenchida.

Com type="email", o teclado do celular já vem com a tecla @, e o navegador confere o formato antes do envio. Com type="tel", aparece o teclado numérico. Com type="date", o usuário ganha o seletor de data do próprio sistema, no lugar de digitar a data à mão e errar o formato.

❌ Ruim: cinco dados diferentes, todos como texto
<input type="text" name="email" />
<input type="text" name="phone" />
<input type="text" name="age" />
<input type="text" name="price" />
<input type="text" name="birthday" />
✅ Bom: cada campo declara o dado que espera, e o navegador ajusta teclado e validação
<input type="email" name="email" autocomplete="email" />
<input type="tel" name="phone" autocomplete="tel" />
<input type="number" name="age" min="0" max="120" />
<input type="number" name="price" min="0" step="0.01" />
<input type="date" name="birthday" />

Declare a regra na marcação antes de escrever validação em JavaScript

Os atributos required, pattern, min, max, minlength e maxlength fazem o navegador barrar o envio e apontar o campo errado sozinho. A regra fica declarada ao lado do campo, onde quem lê a marcação a encontra.

O JavaScript entra por cima disso, para o que a marcação não cobre: a mensagem de erro escrita com as palavras do produto (via setCustomValidity) e a dica que explica o formato antes de o usuário errar. O aria-describedby amarra a dica e o erro ao campo, e é assim que o leitor de tela anuncia os dois.

❌ Ruim: o campo não declara regra nenhuma, e o erro vive escondido num parágrafo solto
<input type="text" id="username" name="username" />
<p id="username-error" class="error hidden">Username is required</p>
✅ Bom: as regras na marcação, com a dica e o erro ligados ao campo
<label for="username">Username</label>
<input
  id="username"
  type="text"
  name="username"
  minlength="3"
  maxlength="20"
  pattern="[a-zA-Z0-9_]+"
  aria-describedby="username-hint username-error"
  required
/>
<p id="username-hint" class="field-hint">3–20 characters, letters and numbers only</p>
<p id="username-error" class="field-error" aria-live="polite"></p>

Referência rápida

Input typeUsoBenefício nativo
emailEndereço de e-mailTeclado @, validação de formato
telTelefoneTeclado numérico mobile
numberValor numéricoControles de incremento, min/max
dateDataDate picker nativo
searchCampo de buscaBotão de limpar, semântica de busca
passwordSenhaMascaramento, autopreenchimento
urlURLValidação de formato
checkboxMúltipla seleçãoEstado checked acessível
radioSeleção exclusiva no grupoGrupo via name, navegação teclado

DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.