Formulários em HTML: guia completo com input, label e validação
Você preenche formulários o tempo todo: para fazer login, buscar algo no Google, pedir comida por aplicativo, se inscrever num curso ou responder a uma pesquisa. Toda vez que um site precisa receber uma informação de você, existe um formulário por trás, e ele é feito com um punhado de tags HTML.
Neste artigo vamos construir, do zero, o formulário de reserva de mesa da Cafeteria Grão Dourado. No caminho, você vai entender cada peça: o <form>, os rótulos, os vários tipos de campo, as escolhas, a organização em grupos e a validação que o próprio navegador faz, sem nenhuma linha de JavaScript.
Como um formulário funciona
Antes das tags, vale entender o caminho que os dados percorrem. O formulário é como uma ficha de papel: cada campo tem um nome impresso (“Nome”, “Telefone”), a pessoa preenche, e a ficha inteira é entregue no balcão. No HTML:
name=valor e envia tudo para o endereço definido no action.Quem recebe e guarda os dados (num banco de dados, num e-mail) é um programa no servidor, feito em PHP, Node.js, Python etc. O HTML cuida da parte da frente: coletar os dados certos, do jeito certo.
A tag <form>: a ficha inteira
Todo formulário começa com <form>, que envolve todos os campos. Ele tem dois atributos principais:
<form action="obrigado.html" method="get">
<!-- os campos ficam aqui dentro -->
</form>action: para onde os dados vão quando o formulário é enviado (uma página ou um programa no servidor).method: como eles vão. Os dois valores sãogetepost.
method="get" | method="post" | |
|---|---|---|
| Onde os dados vão | Na própria URL, depois do ? | “Escondidos” no corpo da requisição |
| Aparece na barra de endereço? | Sim: ?nome=Ana&pessoas=4 | Não |
| Use para | Buscas e filtros (dá para salvar o link) | Cadastros, login, pedidos, senhas |
post esconde os dados da barra de endereço, mas não os criptografa. Quem protege senhas e dados pessoais é o HTTPS (o cadeado do navegador). No primeiro exemplo deste artigo usamos get de propósito: assim você consegue ver o que foi enviado. No teste com envio para o e-mail, mais adiante, usamos post.<label> e <input>: o rótulo e o campo
O campo mais comum é o <input>. Ele é uma tag vazia (não tem tag de fechamento) e muda completamente de comportamento conforme o atributo type. Ao lado dele, sempre, um <label>: o texto que diz o que deve ser preenchido.
for do label tem que ser igual ao id do input. É isso que liga os dois.Por que o label é tão importante?
Quando o for do label bate com o id do campo, três coisas acontecem: clicar no texto coloca o cursor no campo, a área de clique fica maior (ótimo no celular) e os leitores de tela anunciam o nome do campo para pessoas cegas. Teste a diferença:
id e name: qual a diferença?
Eles parecem a mesma coisa, mas têm funções diferentes, e na maioria dos campos usamos o mesmo valor nos dois:
id: identidade única na página. Serve para ligar o<label>ao campo (e para o CSS e o JavaScript encontrarem o elemento).name: o nome do dado que vai no envio. Campo semnameé ignorado: o usuário preenche, mas a informação nunca chega.
Os tipos de input
O mesmo <input> vira um campo de texto, uma data, uma cor ou um controle deslizante só trocando o type. E não é só aparência: no celular, cada tipo abre o teclado certo. O email mostra a tecla @, o tel mostra o teclado numérico. Experimente:
| type | Para quê | Detalhe útil |
|---|---|---|
text | Texto curto (nome, cidade) | É o tipo padrão, se você não colocar type |
email | Endereço de e-mail | Valida se tem @; teclado com @ no celular |
tel | Telefone | Teclado numérico; não valida formato sozinho |
password | Senha | Esconde os caracteres com bolinhas |
number | Quantidades | Aceita min, max e step |
date / time | Data e hora | Abre um calendário ou relógio do próprio navegador |
url | Endereço de site | Exige o endereço completo, com o protocolo (https://) |
search | Campo de busca | Alguns navegadores mostram um “x” para limpar |
range | Escolher um valor numa faixa | Bom quando o número exato não importa |
color | Escolher uma cor | Envia o código hexadecimal, como #1a73c9 |
number. Use number só para quantidades que fazem sentido em contas (pessoas, itens). Telefone, CEP e CPF são códigos: podem começar com zero, têm parênteses e traços. Use tel para telefone e text para os outros.Campos de escolha
Radio: uma opção entre várias
O type="radio" cria as bolinhas em que só uma pode ficar marcada. O segredo é que todas as opções do grupo têm o mesmo name. É isso que avisa ao navegador que elas pertencem ao mesmo grupo. O value é o que vai no envio.
<input type="radio" id="ocasiao-nao" name="ocasiao" value="nenhuma" checked>
<label for="ocasiao-nao">Não, só um café mesmo</label>
<input type="radio" id="ocasiao-aniversario" name="ocasiao" value="aniversario">
<label for="ocasiao-aniversario">Aniversário</label>O atributo checked deixa uma opção marcada de início. Veja o que acontece quando alguém esquece de repetir o name:
Checkbox: marque quantas quiser
O type="checkbox" cria quadradinhos independentes: a pessoa pode marcar nenhum, um ou todos. Também é o campo certo para um “sim ou não” sozinho, como aceitar receber promoções.
<input type="checkbox" id="extra-cadeirinha" name="extras" value="cadeirinha">
<label for="extra-cadeirinha">Cadeirinha para criança</label>
<input type="checkbox" id="extra-tomada" name="extras" value="tomada">
<label for="extra-tomada">Mesa perto de tomada</label>Se as duas forem marcadas, o envio leva extras=cadeirinha&extras=tomada. Checkbox que não foi marcado simplesmente não é enviado.
select: uma lista suspensa
Quando há muitas opções, uma lista suspensa ocupa menos espaço que uma fileira de radios. Cada <option> tem o texto que a pessoa vê e o value que vai no envio:
<label for="ambiente">Ambiente</label>
<select id="ambiente" name="ambiente">
<option value="salao">Salão principal</option>
<option value="varanda">Varanda</option>
<option value="kids">Perto da área kids</option>
</select>textarea: texto longo
Para observações, mensagens e comentários, o <textarea> cria uma caixa de várias linhas. Diferente do <input>, ele tem tag de fechamento, e o tamanho inicial é definido por rows (linhas) e cols (colunas):
<label for="observacoes">Observações</label>
<textarea id="observacoes" name="observacoes" rows="4" cols="40"></textarea>Organizando com <fieldset> e <legend>
Formulário grande assusta. O <fieldset> agrupa campos relacionados numa moldura, e o <legend> dá um título a esse grupo. Para grupos de radio e checkbox ele é especialmente importante: o leitor de tela anuncia a pergunta (“É uma ocasião especial?”) antes de cada opção.
<fieldset>
<legend>Seus dados</legend>
<p>
<label for="nome">Nome completo *</label>
<input type="text" id="nome" name="nome" required>
</p>
...
</fieldset>Repare no <p> em volta de cada par label + campo: é um jeito simples de colocar cada campo na sua própria linha, só com HTML.
Botões: enviar e limpar
<button type="submit">Reservar mesa</button>
<button type="reset">Limpar</button>type="submit": envia o formulário (e dispara a validação).type="reset": volta todos os campos ao estado inicial. Use com cuidado, porque um clique errado apaga tudo.type="button": um botão que não faz nada sozinho, usado com JavaScript.
<form>, um <button> sem type é submit por padrão. Por isso, escreva sempre o type, para nenhum botão enviar o formulário sem querer.Validação: o navegador confere antes de enviar
Sem escrever nenhuma linha de programação, você pode pedir para o navegador conferir os dados. Se algo estiver errado, ele bloqueia o envio, leva o cursor até o campo com problema e mostra uma mensagem. Os atributos principais:
| Atributo | O que confere | Exemplo |
|---|---|---|
required | O campo não pode ficar vazio | <input type="text" required> |
type="email" | Formato de e-mail | ana.email.com não passa (falta o @) |
min / max | Menor e maior valor (números, datas, horários) | min="1" max="12" |
minlength / maxlength | Quantidade de caracteres | minlength="3" |
pattern | Um formato específico (expressão regular) | pattern="[0-9]{5}-[0-9]{3}" para CEP |
Teste agora. Tente enviar vazio, com um e-mail sem @ e com 20 pessoas. Quando tudo estiver certo, veja exatamente o que o navegador enviaria:
Repare na saída: os campos viram pares name=valor separados por &, e o checkbox só aparece se estiver marcado. Alguns caracteres são “traduzidos” para viajar na URL: o espaço vira + e o @ vira %40. É exatamente o que você verá na barra de endereço ao testar o projeto completo, já que ele usa method="get".
Teste de verdade: receba a reserva no seu e-mail
Até aqui, os dados só “passeiam” entre duas páginas. Para recebê-los de verdade, alguém precisa ficar do outro lado: um programa no servidor. Como ainda não vamos programar o servidor, dá para usar o FormSubmit (formsubmit.co), um serviço gratuito que recebe o envio do formulário e repassa tudo para o seu e-mail. Sem cadastro e sem hospedagem.
Passo a passo
- Troque o
actione omethod. Oactionpassa a ser o endereço do FormSubmit seguido do seu e-mail, e omethodpassa a serpost, o único que o serviço aceita. - Abra a página por um servidor, não com dois cliques. A ajuda do FormSubmit avisa que ele não funciona em páginas abertas direto como arquivo (
file://). No VS Code, instale a extensão Live Server e clique em Go Live na barra de baixo. A página abre emhttp://127.0.0.1:5500, um servidor rodando no seu próprio computador. - Envie uma vez e ative. No primeiro envio, o FormSubmit manda para o seu e-mail um pedido de confirmação. Clique no botão de ativação. Antes disso, nenhum envio chega.
- Envie de novo. Agora a reserva chega na sua caixa de entrada, com cada
namee o valor preenchido.
<form action="https://formsubmit.co/SEU-EMAIL@exemplo.com" method="post">
<!-- Campos ocultos: configuram o FormSubmit e não aparecem na tela -->
<input type="hidden" name="_subject" value="Nova reserva de mesa — Grão Dourado">
<input type="hidden" name="_template" value="table">
<!-- ... o resto do formulário continua igual ... -->
</form>O campo invisível: type=”hidden”
O type="hidden" cria um campo que não aparece na tela, mas vai junto no envio, com o name e o value que você definiu. O FormSubmit usa alguns nomes especiais, que começam com _, como configurações:
| name | O que faz | Exemplo de value |
|---|---|---|
_subject | Assunto do e-mail que você recebe | Nova reserva de mesa |
_template | Organiza os dados do e-mail numa tabela | table |
_next | Página para onde a pessoa vai depois de enviar (endereço completo) | http://127.0.0.1:5500/obrigado.html |
_cc | Envia uma cópia para outro e-mail | professor@exemplo.com |
Sem o _next, o FormSubmit mostra uma página de agradecimento própria. O FormSubmit também coloca uma verificação anti-robô (“não sou um robô”) antes de concluir o envio. Deixe ela ligada: é o que protege o seu e-mail de spam.
Boas práticas que fazem diferença
- Placeholder não é label. O texto cinza do
placeholdersome quando a pessoa começa a digitar, e aí ela esquece o que era o campo. Use o placeholder só para um exemplo (“voce@email.com”), nunca no lugar do<label>. - Avise o que é obrigatório. Um
*no label e uma frase no topo (“Campos marcados com * são obrigatórios”) evitam surpresas. - Use
autocomplete. Comautocomplete="name","email"ou"tel", o navegador oferece os dados que a pessoa já salvou. São menos toques no celular. - Peça só o necessário. Cada campo a mais é um motivo para a pessoa desistir no meio. Precisa mesmo do CPF para reservar uma mesa?
Erros comuns
nameO campo aparece, a pessoa preenche, mas o dado não é enviado. Todo campo que precisa chegar ao servidor tem que ter name.
for diferente do id<label for="e-mail"> com <input id="email"> não se ligam: uma letra ou um hífen de diferença já quebra a ligação. Copie e cole o valor para não errar.
name diferentesCada opção vira um grupo sozinho, todas podem ser marcadas e nenhuma pode ser desmarcada. Opções do mesmo grupo sempre têm o mesmo name.
value em radio e checkboxSem value, o navegador envia apenas on, e o servidor não sabe qual opção foi escolhida.
<form>Um <input> colocado depois do </form> não é enviado junto com os outros. Confira se a tag de fechamento está no lugar certo.
Com o endereço começando por file://, o envio não funciona. Abra pelo Live Server (http://127.0.0.1:5500) e confira se o method está como post.
Código completo do exemplo
O projeto tem o formulário, a página de “obrigado”, que recebe o envio, a versão com FormSubmit e um arquivo de estilo (estilo.css) compartilhado pelas três páginas. Salve tudo na mesma pasta e abra o reserva.html no navegador:
formulario-reserva/
├── reserva.html (envio com GET, para ver os dados na URL)
├── obrigado.html
├── reserva-formsubmit.html (mesmo formulário, enviando para o seu e-mail)
└── estilo.css (aparência das três páginas)reserva.html<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reserva de mesa — Cafeteria Grão Dourado</title>
<link rel="stylesheet" href="estilo.css">
</head>
<body>
<h1>☕ Cafeteria Grão Dourado</h1>
<h2>Reserve sua mesa</h2>
<p>Campos marcados com * são obrigatórios.</p>
<!-- action: para onde os dados vão | method: como eles vão -->
<form action="obrigado.html" method="get">
<!-- GRUPO 1: dados da pessoa -->
<fieldset>
<legend>Seus dados</legend>
<p>
<label for="nome">Nome completo *</label>
<input class="campo" type="text" id="nome" name="nome" autocomplete="name" minlength="3" required>
</p>
<p>
<label for="email">E-mail *</label>
<input class="campo" type="email" id="email" name="email" autocomplete="email" placeholder="voce@email.com" required>
</p>
<p>
<label for="telefone">Celular</label>
<input class="campo" type="tel" id="telefone" name="telefone" autocomplete="tel" placeholder="(11) 91234-5678">
</p>
</fieldset>
<!-- GRUPO 2: dados da reserva -->
<fieldset>
<legend>Sua reserva</legend>
<p>
<label for="data">Data *</label>
<input class="campo" type="date" id="data" name="data" required>
</p>
<p>
<label for="horario">Horário * (das 7h às 19h)</label>
<input class="campo" type="time" id="horario" name="horario" min="07:00" max="19:00" required>
</p>
<p>
<label for="pessoas">Número de pessoas *</label>
<input class="campo" type="number" id="pessoas" name="pessoas" min="1" max="12" value="2" required>
</p>
<p>
<label for="ambiente">Ambiente</label>
<select class="campo" id="ambiente" name="ambiente">
<option value="salao">Salão principal</option>
<option value="varanda">Varanda</option>
<option value="kids">Perto da área kids</option>
</select>
</p>
</fieldset>
<!-- GRUPO 3: escolha única (radio) — todos com o MESMO name -->
<fieldset>
<legend>É uma ocasião especial?</legend>
<p>
<input type="radio" id="ocasiao-nao" name="ocasiao" value="nenhuma" checked>
<label for="ocasiao-nao">Não, só um café mesmo</label>
</p>
<p>
<input type="radio" id="ocasiao-aniversario" name="ocasiao" value="aniversario">
<label for="ocasiao-aniversario">Aniversário</label>
</p>
<p>
<input type="radio" id="ocasiao-trabalho" name="ocasiao" value="trabalho">
<label for="ocasiao-trabalho">Reunião de trabalho</label>
</p>
</fieldset>
<!-- GRUPO 4: várias escolhas (checkbox) e texto livre -->
<fieldset>
<legend>Precisa de algo a mais?</legend>
<p>
<input type="checkbox" id="extra-cadeirinha" name="extras" value="cadeirinha">
<label for="extra-cadeirinha">Cadeirinha para criança</label>
</p>
<p>
<input type="checkbox" id="extra-acessivel" name="extras" value="acessivel">
<label for="extra-acessivel">Mesa com acesso para cadeira de rodas</label>
</p>
<p>
<input type="checkbox" id="extra-tomada" name="extras" value="tomada">
<label for="extra-tomada">Mesa perto de tomada</label>
</p>
<p>
<label for="observacoes">Observações</label><br>
<textarea class="campo" id="observacoes" name="observacoes" rows="4" cols="40"
placeholder="Ex.: vamos levar um bolo de aniversário"></textarea>
</p>
</fieldset>
<p>
<input type="checkbox" id="promocoes" name="promocoes" value="sim">
<label for="promocoes">Quero receber as promoções da semana por e-mail</label>
</p>
<!-- BOTÕES -->
<p>
<button class="btn-enviar" type="submit">Reservar mesa</button>
<button class="btn-limpar" type="reset">Limpar</button>
</p>
</form>
</body>
</html>obrigado.html<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reserva recebida — Cafeteria Grão Dourado</title>
<link rel="stylesheet" href="estilo.css">
</head>
<body>
<h1 class="centro">Reserva recebida! ☕</h1>
<p>Obrigado! Em breve confirmaremos sua mesa por e-mail.</p>
<!-- Para a aula: olhe a barra de endereço do navegador.
Com method="get", os dados do formulário aparecem na URL, depois do "?",
no formato name=valor, separados por "&". -->
<p class="destaque"><strong>Olhe a barra de endereço do navegador:</strong> tudo o que você
preencheu está lá, depois do <code>?</code>, no formato <code>name=valor</code>.</p>
<p><a href="reserva.html">Fazer outra reserva</a></p>
</body>
</html>estilo.css/* estilo.css — Cafeteria Grão Dourado
Usado por: reserva.html, reserva-formsubmit.html e obrigado.html */
/* Faz width e padding caberem juntos (aula do Box Model) */
* {
box-sizing: border-box;
}
/* Página */
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
color: #2b2d42;
max-width: 600px;
margin: 0 auto;
padding: 20px;
line-height: 1.5;
}
/* Títulos */
h1 {
border-bottom: 4px solid #f68e36;
padding-bottom: 8px;
}
h2 {
color: #1a73c9;
}
.centro {
text-align: center;
}
/* Grupos do formulário */
fieldset {
background-color: white;
border: 2px solid #369ef6;
border-radius: 8px;
padding: 12px 20px;
margin-bottom: 20px;
}
legend {
color: #1a73c9;
font-weight: bold;
padding: 0 8px;
}
/* Rótulos: o cursor de mãozinha mostra que clicar no texto marca o campo */
label {
font-weight: bold;
cursor: pointer;
}
/* Campos de texto, data, número, lista e área de texto (class="campo") */
.campo {
width: 100%;
padding: 10px;
margin-top: 4px;
border: 2px solid #b8c4d6;
border-radius: 6px;
font-size: 16px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.campo:hover {
border-color: #369ef6;
}
.campo:focus {
border-color: #1a73c9;
outline: none;
box-shadow: 0 0 0 3px rgba(54, 158, 246, 0.35);
}
/* Botões */
button {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.btn-enviar {
background-color: #f68e36;
color: #2b2d42;
}
.btn-enviar:hover {
background-color: #fab875;
transform: translateY(-2px);
}
.btn-limpar {
background-color: #e3e7ee;
color: #2b2d42;
}
.btn-limpar:hover {
background-color: #cfd6e2;
}
/* Contorno para quem navega pelo teclado */
button:focus-visible {
outline: 3px solid #1a73c9;
outline-offset: 2px;
}
/* Página de obrigado */
.destaque {
background-color: #fff6ec;
border-left: 5px solid #f68e36;
border-radius: 4px;
padding: 12px 16px;
}
code {
background-color: #fff1e3;
color: #a84b07;
padding: 2px 6px;
border-radius: 4px;
}
a {
color: #1a73c9;
font-weight: bold;
transition: color 0.3s ease;
}
a:hover {
color: #b85309;
}Repare que o HTML não mudou de estrutura: só ganhou o <link rel="stylesheet" href="estilo.css"> no <head> e algumas classes (campo, btn-enviar, btn-limpar, centro, destaque). Se quiser ver a diferença, comente a linha do <link> e recarregue: sem CSS, o formulário volta à aparência padrão do navegador, simples, mas completo e funcionando. O mesmo estilo.css também serve para o reserva-formsubmit.html.
O arquivo reaproveita o que você já viu na série: box-sizing e padding do Box Model, class para escolher os campos e botões, e :hover, :focus, transition e cursor para dar resposta visual ao usuário.
Resumo rápido
| Tag ou atributo | Para que serve |
|---|---|
<form action method> | Envolve os campos; define para onde e como os dados vão |
<label for> | Rótulo do campo; o for aponta para o id |
<input type> | Campo de entrada; o type define o comportamento |
name | Nome do dado no envio (sem ele, nada é enviado) |
radio / checkbox | Uma escolha (mesmo name) / várias escolhas |
<select> + <option> | Lista suspensa |
<textarea> | Texto de várias linhas |
<fieldset> + <legend> | Agrupa campos relacionados com um título |
required, min, max, minlength, pattern | Validação feita pelo navegador |
<button type> | submit envia, reset limpa, button não faz nada sozinho |
Desafio para praticar
- Monte o projeto, preencha a reserva e envie. Na página de obrigado, encontre cada campo na barra de endereço. Qual campo não apareceu e por quê?
- Apague o
namedo campo de telefone, envie de novo e compare a barra de endereço. - Adicione um campo de CEP com
pattern="[0-9]{5}-[0-9]{3}"eplaceholder="08675-000". Teste com e sem o hífen. - Crie um novo grupo de radio, “Como conheceu a cafeteria?”, com quatro opções e um
<fieldset>próprio. - Desafio extra: coloque o seu e-mail no
reserva-formsubmit.html, abra pelo Live Server e receba a reserva na sua caixa de entrada. O que mudou na barra de endereço em relação ao envio comget?
Para saber mais
- web.dev: Formulários (em português)
- MDN: Your first form
- MDN: How to structure a web form
- MDN: HTML5 input types
- W3Schools: HTML Forms
- FormSubmit: receba formulários no seu e-mail
Washington Paiva — instrutor de Desenvolvimento de Sistemas · washington.pro.br
Compartilhe:




Publicar comentário