Formulários são a principal forma de comunicação entre o usuário e uma aplicação web. Login, cadastro, checkout, busca, contato — tudo passa por formulários. E trabalhar bem com eles exige muito mais do que um <form> no HTML.
Neste artigo vamos aprender a coletar dados de formulários, validar em tempo real, dar feedback visual e entregar uma experiência de qualidade ao usuário — tudo isso sem usar nenhuma biblioteca externa.
Estrutura HTML base
Vamos trabalhar com este formulário de cadastro ao longo do artigo:
<form id="form-cadastro" novalidate>
<div class="campo">
<label for="nome">Nome completo</label>
<input type="text" id="nome" name="nome" placeholder="Seu nome completo">
<span class="erro" id="erro-nome"></span>
</div>
<div class="campo">
<label for="email">E-mail</label>
<input type="email" id="email" name="email" placeholder="seu@email.com">
<span class="erro" id="erro-email"></span>
</div>
<div class="campo">
<label for="senha">Senha</label>
<input type="password" id="senha" name="senha" placeholder="Mínimo 8 caracteres">
<span class="erro" id="erro-senha"></span>
</div>
<div class="campo">
<label for="confirmar">Confirmar senha</label>
<input type="password" id="confirmar" name="confirmar" placeholder="Repita a senha">
<span class="erro" id="erro-confirmar"></span>
</div>
<div class="campo">
<label for="perfil">Perfil</label>
<select id="perfil" name="perfil">
<option value="">Selecione...</option>
<option value="estudante">Estudante</option>
<option value="profissional">Profissional</option>
<option value="empresa">Empresa</option>
</select>
<span class="erro" id="erro-perfil"></span>
</div>
<div class="campo campo-check">
<input type="checkbox" id="termos" name="termos">
<label for="termos">Li e aceito os termos de uso</label>
<span class="erro" id="erro-termos"></span>
</div>
<button type="submit" id="btn-enviar">Criar conta</button>
</form>
O atributo novalidate no <form> desativa a validação nativa do navegador — queremos controlar tudo nós mesmos.
Coletando dados do formulário
Existem várias formas de coletar dados. Veja as principais:
// Acessando campos individualmente
const nome = document.querySelector("#nome").value;
const email = document.querySelector("#email").value;
// FormData — coleta todos os campos de uma vez
const form = document.querySelector("#form-cadastro");
form.addEventListener("submit", (e) => {
e.preventDefault();
const dados = new FormData(form);
// Acessar um campo pelo name
console.log(dados.get("nome"));
console.log(dados.get("email"));
// Converter para objeto simples
const objeto = Object.fromEntries(dados.entries());
console.log(objeto);
// { nome: "Ana", email: "ana@email.com", senha: "...", ... }
});
FormData é especialmente útil quando o formulário tem muitos campos — você não precisa selecionar cada um individualmente.
Tipos de campos e como ler seus valores
// Input de texto, email, password, number
const valor = document.querySelector("#nome").value; // string
// Checkbox
const aceito = document.querySelector("#termos").checked; // boolean
// Radio buttons — qual está marcado?
const genero = document.querySelector('input[name="genero"]:checked');
const valorGenero = genero ? genero.value : null;
// Select
const select = document.querySelector("#perfil");
const opcaoSelecionada = select.value; // valor da option
const textoSelecionado = select.options[select.selectedIndex].text;
// Select múltiplo
const selectMultiplo = document.querySelector("#interesses");
const selecionados = Array.from(selectMultiplo.selectedOptions)
.map(opt => opt.value);
// Input de arquivo
const arquivo = document.querySelector("#foto").files[0];
console.log(arquivo.name, arquivo.size, arquivo.type);
Sistema de validação
Vamos construir um sistema de validação modular e reutilizável:
// ── Regras de validação ────────────────────────────
const regras = {
nome(valor) {
if (!valor.trim()) return "Nome é obrigatório.";
if (valor.trim().length < 3) return "Nome deve ter pelo menos 3 caracteres.";
if (valor.trim().length > 80) return "Nome deve ter no máximo 80 caracteres.";
if (!/^[a-zA-ZÀ-ÿ\s]+$/.test(valor)) return "Nome deve conter apenas letras.";
return null; // null = válido
},
email(valor) {
if (!valor.trim()) return "E-mail é obrigatório.";
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!regex.test(valor)) return "E-mail inválido.";
return null;
},
senha(valor) {
if (!valor) return "Senha é obrigatória.";
if (valor.length < 8) return "Senha deve ter pelo menos 8 caracteres.";
if (!/[A-Z]/.test(valor)) return "Senha deve conter ao menos uma letra maiúscula.";
if (!/[0-9]/.test(valor)) return "Senha deve conter ao menos um número.";
return null;
},
confirmar(valor) {
const senha = document.querySelector("#senha").value;
if (!valor) return "Confirmação de senha é obrigatória.";
if (valor !== senha) return "As senhas não coincidem.";
return null;
},
perfil(valor) {
if (!valor) return "Selecione um perfil.";
return null;
},
termos(valor) {
const checkbox = document.querySelector("#termos");
if (!checkbox.checked) return "Você deve aceitar os termos de uso.";
return null;
},
};
Funções de feedback visual
// ── Funções de UI ──────────────────────────────────
function mostrarErro(campo, mensagem) {
const input = document.querySelector(`#${campo}`);
const erroSpan = document.querySelector(`#erro-${campo}`);
input.classList.remove("valido");
input.classList.add("invalido");
erroSpan.textContent = mensagem;
}
function mostrarSucesso(campo) {
const input = document.querySelector(`#${campo}`);
const erroSpan = document.querySelector(`#erro-${campo}`);
input.classList.remove("invalido");
input.classList.add("valido");
erroSpan.textContent = "";
}
function limparFeedback(campo) {
const input = document.querySelector(`#${campo}`);
const erroSpan = document.querySelector(`#erro-${campo}`);
input.classList.remove("valido", "invalido");
erroSpan.textContent = "";
}
// ── Validação de campo individual ─────────────────
function validarCampo(nomeCampo) {
const input = document.querySelector(`#${nomeCampo}`);
const valor = input.type === "checkbox" ? input.checked : input.value;
const erro = regras[nomeCampo](valor);
if (erro) {
mostrarErro(nomeCampo, erro);
return false;
} else {
mostrarSucesso(nomeCampo);
return true;
}
}
Validação em tempo real no blur
// ── Eventos de validação em tempo real ────────────
const campos = ["nome", "email", "senha", "confirmar", "perfil"];
campos.forEach(campo => {
const input = document.querySelector(`#${campo}`);
// Valida quando o campo perde foco
input.addEventListener("blur", () => {
validarCampo(campo);
});
// Limpa o erro enquanto digita (após ter sido tocado)
input.addEventListener("input", () => {
if (input.classList.contains("invalido")) {
validarCampo(campo);
}
});
});
// Checkbox de termos
document.querySelector("#termos").addEventListener("change", () => {
validarCampo("termos");
});
Validando o formulário completo no submit
// ── Submit ─────────────────────────────────────────
const form = document.querySelector("#form-cadastro");
const btnEnviar = document.querySelector("#btn-enviar");
form.addEventListener("submit", (e) => {
e.preventDefault();
// Valida todos os campos
const todosCampos = ["nome", "email", "senha", "confirmar", "perfil", "termos"];
const resultados = todosCampos.map(campo => validarCampo(campo));
const formValido = resultados.every(resultado => resultado === true);
if (!formValido) {
// Foca no primeiro campo inválido
const primeiroInvalido = form.querySelector(".invalido");
if (primeiroInvalido) primeiroInvalido.focus();
return;
}
// Coleta os dados
const formData = new FormData(form);
const dados = Object.fromEntries(formData.entries());
// Remove a confirmação de senha — não precisamos enviar
delete dados.confirmar;
// Simula envio
enviarFormulario(dados);
});
async function enviarFormulario(dados) {
const btn = document.querySelector("#btn-enviar");
// Estado de carregamento
btn.disabled = true;
btn.textContent = "Criando conta...";
// Simula uma requisição (aprenderemos fetch no Módulo 3)
await new Promise(resolve => setTimeout(resolve, 1500));
console.log("Dados enviados:", dados);
// Feedback de sucesso
form.innerHTML = `
<div class="sucesso">
<div class="icone-sucesso">✅</div>
<h2>Conta criada com sucesso!</h2>
<p>Bem-vindo, <strong>${dados.nome}</strong>!</p>
<p>Um e-mail de confirmação foi enviado para <strong>${dados.email}</strong>.</p>
</div>
`;
}
Força da senha em tempo real
Um recurso muito comum — e útil para demonstrar como reagir a cada caractere digitado:
const inputSenha = document.querySelector("#senha");
const barraForca = document.createElement("div");
barraForca.id = "barra-forca";
// Insere a barra após o input de senha
inputSenha.after(barraForca);
function calcularForca(senha) {
let pontos = 0;
if (senha.length >= 8) pontos++;
if (senha.length >= 12) pontos++;
if (/[A-Z]/.test(senha)) pontos++;
if (/[a-z]/.test(senha)) pontos++;
if (/[0-9]/.test(senha)) pontos++;
if (/[^A-Za-z0-9]/.test(senha)) pontos++; // caractere especial
return pontos;
}
function atualizarBarraForca(senha) {
const forca = calcularForca(senha);
const niveis = [
{ min: 0, label: "", cor: "transparent", largura: "0%" },
{ min: 1, label: "Muito fraca", cor: "#e53935", largura: "20%" },
{ min: 2, label: "Fraca", cor: "#fb8c00", largura: "40%" },
{ min: 3, label: "Razoável", cor: "#fdd835", largura: "60%" },
{ min: 4, label: "Boa", cor: "#43a047", largura: "80%" },
{ min: 5, label: "Forte", cor: "#1b5e20", largura: "100%" },
];
const nivel = niveis[Math.min(forca, niveis.length - 1)];
barraForca.innerHTML = `
<div style="
height: 4px;
background: #eee;
border-radius: 4px;
margin-top: .35rem;
overflow: hidden;
">
<div style="
height: 100%;
width: ${nivel.largura};
background: ${nivel.cor};
border-radius: 4px;
transition: width .3s, background .3s;
"></div>
</div>
${senha ? `<small style="color: ${nivel.cor}; font-weight: 600;">${nivel.label}</small>` : ""}
`;
}
inputSenha.addEventListener("input", (e) => {
atualizarBarraForca(e.target.value);
});
O CSS do formulário
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', sans-serif;
background: #f0f2f5;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 2rem 1rem;
}
form {
background: white;
padding: 2rem;
border-radius: 12px;
width: 100%;
max-width: 480px;
box-shadow: 0 4px 24px rgba(0,0,0,.08);
}
.campo {
margin-bottom: 1.25rem;
display: flex;
flex-direction: column;
gap: .35rem;
}
label {
font-size: .9rem;
font-weight: 600;
color: #333;
}
input, select {
padding: .7rem 1rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: border-color .2s, box-shadow .2s;
}
input:focus, select:focus {
outline: none;
border-color: #5c6bc0;
box-shadow: 0 0 0 3px rgba(92,107,192,.15);
}
input.valido { border-color: #43a047; }
input.invalido { border-color: #e53935; }
.erro {
font-size: .8rem;
color: #e53935;
min-height: 1rem;
}
.campo-check {
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: .5rem;
}
.campo-check label { font-weight: 400; }
button[type="submit"] {
width: 100%;
padding: .85rem;
background: #5c6bc0;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
margin-top: .5rem;
transition: background .2s;
}
button[type="submit"]:hover:not(:disabled) { background: #3949ab; }
button[type="submit"]:disabled { opacity: .6; cursor: not-allowed; }
.sucesso {
text-align: center;
padding: 2rem 1rem;
}
.icone-sucesso { font-size: 3rem; margin-bottom: 1rem; }
.sucesso h2 { margin-bottom: .5rem; color: #1b5e20; }
.sucesso p { color: #555; margin-top: .5rem; }
Boas práticas com formulários
// ✅ 1. Sempre use e.preventDefault() no submit
form.addEventListener("submit", (e) => {
e.preventDefault();
// ...
});
// ✅ 2. Valide no cliente E no servidor — nunca só no cliente
// A validação no front-end é para UX — a do back-end é para segurança
// ✅ 3. Dê feedback imediato e específico
// ❌ "Dados inválidos." — inútil
// ✅ "O e-mail deve estar no formato nome@dominio.com" — útil
// ✅ 4. Nunca desabilite o botão de submit antes do usuário tentar
// Desabilite apenas durante o envio
// ✅ 5. Preserve os dados do usuário em caso de erro
// Nunca limpe o formulário quando há erro de validação
// ✅ 6. Use o atributo autocomplete apropriado
// <input autocomplete="email"> — ajuda o gerenciador de senhas e preenchimento
// ✅ 7. Associe labels aos inputs com for/id
// <label for="email"> + <input id="email"> — acessibilidade e UX
Tarefa para você
Crie um formulário de cálculo de financiamento com os campos:
- Valor do imóvel (number, mínimo R$ 50.000)
- Entrada (number, mínimo 20% do valor)
- Prazo em meses (select: 60, 120, 180, 240, 360)
- Taxa de juros anual (number, entre 0.1% e 20%)
- Nome do solicitante (text)
Ao submeter (com todos os campos válidos), exiba abaixo do formulário:
Resumo do Financiamento
- Valor financiado: R$ X
- Parcela mensal estimada: R$ X (usando a fórmula Price)
- Total pago: R$ X
- Total de juros: R$ X
A fórmula Price para cálculo de parcela:
// PMT = PV * (i * (1 + i)^n) / ((1 + i)^n - 1)
// PV = valor financiado, i = taxa mensal, n = prazo em meses
const taxaMensal = taxaAnual / 100 / 12;
const parcela = valorFinanciado *
(taxaMensal * Math.pow(1 + taxaMensal, prazo)) /
(Math.pow(1 + taxaMensal, prazo) - 1);
Conclusão
Neste artigo você aprendeu:
- Como coletar dados de diferentes tipos de campos
FormDataeObject.fromEntriespara coleta eficiente- Como construir um sistema de validação modular
- Feedback visual com classes CSS e mensagens de erro
- Validação em tempo real no
blureinput - Como implementar indicador de força de senha
- Estados do botão durante o envio
- Boas práticas de UX e segurança em formulários
No próximo artigo vamos aprender sobre LocalStorage e SessionStorage — como salvar dados no navegador do usuário e manter o estado da aplicação entre sessões.
📌 Próximo artigo: Aula 15 — LocalStorage e SessionStorage
📚 Fontes e Referências
- MDN Web Docs — HTMLFormElement: https://developer.mozilla.org/pt-BR/docs/Web/API/HTMLFormElement
- MDN Web Docs — FormData: https://developer.mozilla.org/pt-BR/docs/Web/API/FormData
- MDN Web Docs — Constraint Validation: https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
- MDN Web Docs — input event: https://developer.mozilla.org/pt-BR/docs/Web/API/HTMLElement/input_event
- JavaScript.info — Forms and Controls: https://javascript.info/forms-controls
- JavaScript.info — Form Validation: https://javascript.info/form-validation
- Eloquent JavaScript, Cap. 18 — HTTP and Forms: https://eloquentjavascript.net/18_http.html
- Web.dev — Learn Forms: https://web.dev/learn/forms