Validação de Documentos Brasileiros em Formulários (2026): CPF, CNPJ, CEP, Telefone
Formulário web brasileiro tem peculiaridades que frustram usuários e desenvolvedores: CPF aceita dígito verificador inválido, CNPJ muda formato, telefone tem celular com 9 dígito, CEP preenche endereço. Este guia cobre validação e máscara de todos os documentos comuns, com código pronto.
Por Vitor Morais
Fundador do MochaLabz ·
Formulário web brasileiro tem uma coleção de peculiaridades que frustra usuários e desenvolvedores: CPF que passa em regex mas falha no módulo 11, CNPJ que muda formato dependendo do layout, telefone celular com ou sem 9 inicial, CEP que preenche endereço via API, PIS que poucos sabem validar. Cada um desses documentos tem suas regras — tratar todos igualmente é garantia de bug.
Este guia cobre os documentos mais comuns (CPF, CNPJ, CEP, telefone, PIS, RG), validação client-side com UX correta, regex e algoritmos de módulo 11, máscara em tempo real, integração com API de CEP e estratégias de backend para revalidar com segurança.
Princípios gerais antes de cada documento
Frontend valida para UX; backend valida para segurança
Todo formulário sério tem validação dupla. Frontend evita que usuário envie dados óbvios incorretos (feedback em tempo real, reduz frustração). Backend é a verdade — usuário malicioso pode passar frontend facilmente.
Normalize antes de validar
Remova tudo que não é dígito antes de validar CPF, CNPJ, CEP, telefone. O usuário pode colar com ou sem formatação — sua lógica deve aceitar os dois.
Armazene limpo, exiba formatado
Banco guarda 11 dígitos do CPF (string “12345678909”). Interface mostra “123.456.789-09” aplicando máscara no render. Economiza espaço, evita inconsistência e facilita queries.
CPF: módulo 11 + regra de dígitos iguais
Regex de formato
const cpfRegex = /^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$/;
cpfRegex.test("123.456.789-09"); // true
cpfRegex.test("12345678909"); // true (sem formatação)
cpfRegex.test("123.456.789.09"); // false (formato errado)
cpfRegex.test("000.000.000-00"); // true (formato OK mas inválido!)Atenção
000.000.000-00, 111.111.111-11 e outras sequências repetidas. Esses CPFs passam no módulo 11 mas não existem na Receita Federal. Descartar sequências repetidas é obrigatório — ver função completa abaixo.Validação completa com módulo 11
export function validarCPF(cpf: string): boolean {
// Normaliza: remove tudo que não é dígito
const digitos = cpf.replace(/\D/g, "");
// CPF tem 11 dígitos
if (digitos.length !== 11) return false;
// Rejeita sequências repetidas (000...00, 111...11, etc.)
if (/^(\d)\1{10}$/.test(digitos)) return false;
// Calcula primeiro dígito verificador
let sum = 0;
for (let i = 0; i < 9; i++) {
sum += parseInt(digitos[i]) * (10 - i);
}
let d1 = 11 - (sum % 11);
if (d1 >= 10) d1 = 0;
if (d1 !== parseInt(digitos[9])) return false;
// Calcula segundo dígito verificador
sum = 0;
for (let i = 0; i < 10; i++) {
sum += parseInt(digitos[i]) * (11 - i);
}
let d2 = 11 - (sum % 11);
if (d2 >= 10) d2 = 0;
return d2 === parseInt(digitos[10]);
}
// Uso
validarCPF("123.456.789-09"); // true (exemplo válido)
validarCPF("000.000.000-00"); // false (repetição)
validarCPF("123.456.789-00"); // false (dígito errado)Máscara com IMask (React)
import { IMaskInput } from "react-imask";
import { useState } from "react";
export function InputCPF() {
const [value, setValue] = useState("");
const [error, setError] = useState<string | null>(null);
const handleBlur = () => {
const digitos = value.replace(/\D/g, "");
if (digitos.length === 0) {
setError(null);
return;
}
if (digitos.length !== 11 || !validarCPF(value)) {
setError("CPF inválido");
} else {
setError(null);
}
};
return (
<div>
<label htmlFor="cpf">CPF</label>
<IMaskInput
id="cpf"
mask="000.000.000-00"
value={value}
onAccept={(val: string) => setValue(val)}
onBlur={handleBlur}
placeholder="000.000.000-00"
aria-invalid={!!error}
aria-describedby={error ? "cpf-error" : undefined}
/>
{error && (
<span id="cpf-error" className="text-red-600 text-sm">
{error}
</span>
)}
</div>
);
}CNPJ: 14 dígitos e pesos diferentes
Regex de formato
const cnpjRegex = /^\d{2}\.?\d{3}\.?\d{3}\/?\d{4}-?\d{2}$/;
cnpjRegex.test("12.345.678/0001-90"); // true
cnpjRegex.test("12345678000190"); // true (sem formatação)Validação completa
export function validarCNPJ(cnpj: string): boolean {
const digitos = cnpj.replace(/\D/g, "");
if (digitos.length !== 14) return false;
if (/^(\d)\1{13}$/.test(digitos)) return false;
// Pesos do primeiro dígito verificador
const pesos1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let sum = 0;
for (let i = 0; i < 12; i++) {
sum += parseInt(digitos[i]) * pesos1[i];
}
let d1 = 11 - (sum % 11);
if (d1 >= 10) d1 = 0;
if (d1 !== parseInt(digitos[12])) return false;
// Pesos do segundo dígito verificador
const pesos2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
sum = 0;
for (let i = 0; i < 13; i++) {
sum += parseInt(digitos[i]) * pesos2[i];
}
let d2 = 11 - (sum % 11);
if (d2 >= 10) d2 = 0;
return d2 === parseInt(digitos[13]);
}Máscara CNPJ
<IMaskInput
mask="00.000.000/0000-00"
value={value}
onAccept={setValue}
placeholder="00.000.000/0000-00"
/>CEP: validação simples + busca automática
Regex
const cepRegex = /^\d{5}-?\d{3}$/;
cepRegex.test("01001-000"); // true
cepRegex.test("01001000"); // true
cepRegex.test("01001-00"); // falseBusca de endereço via ViaCEP
interface EnderecoViaCEP {
logradouro: string;
bairro: string;
localidade: string; // cidade
uf: string;
erro?: boolean;
}
export async function buscarCEP(cep: string): Promise<EnderecoViaCEP | null> {
const digitos = cep.replace(/\D/g, "");
if (digitos.length !== 8) return null;
try {
const res = await fetch(`https://viacep.com.br/ws/${digitos}/json/`);
if (!res.ok) return null;
const data = await res.json();
if (data.erro) return null;
return data;
} catch {
return null;
}
}Formulário com preenchimento automático
"use client";
import { useState } from "react";
export function FormularioEndereco() {
const [cep, setCep] = useState("");
const [logradouro, setLogradouro] = useState("");
const [bairro, setBairro] = useState("");
const [cidade, setCidade] = useState("");
const [uf, setUf] = useState("");
const [loading, setLoading] = useState(false);
const handleCepBlur = async () => {
setLoading(true);
const endereco = await buscarCEP(cep);
if (endereco) {
setLogradouro(endereco.logradouro);
setBairro(endereco.bairro);
setCidade(endereco.localidade);
setUf(endereco.uf);
}
setLoading(false);
};
return (
<form>
<IMaskInput
mask="00000-000"
value={cep}
onAccept={setCep}
onBlur={handleCepBlur}
placeholder="00000-000"
/>
{loading && <span>Buscando...</span>}
<input value={logradouro} onChange={(e) => setLogradouro(e.target.value)} placeholder="Rua" />
<input value={bairro} onChange={(e) => setBairro(e.target.value)} placeholder="Bairro" />
<input value={cidade} onChange={(e) => setCidade(e.target.value)} placeholder="Cidade" />
<input value={uf} onChange={(e) => setUf(e.target.value)} placeholder="UF" maxLength={2} />
<input placeholder="Número" />
<input placeholder="Complemento (opcional)" />
</form>
);
}Dica
Telefone brasileiro: celular vs fixo
Estrutura
- Celular (após 2018): 11 dígitos — (DD) + 9 + 8 dígitos. Ex: (11) 98765-4321.
- Fixo: 10 dígitos — (DD) + 4 dígitos iniciais + 4 finais. Ex: (11) 3456-7890.
Regex flexível para ambos
const telefoneRegex = /^\(?\d{2}\)?\s?9?\d{4,5}-?\d{4}$/;
// Casos aceitos
telefoneRegex.test("(11) 98765-4321"); // celular
telefoneRegex.test("(11) 3456-7890"); // fixo
telefoneRegex.test("11987654321"); // sem formatação
telefoneRegex.test("1134567890"); // fixo sem formataçãoValidação específica
export function validarTelefoneBR(telefone: string): {
valido: boolean;
tipo?: "celular" | "fixo";
normalizado?: string;
} {
const digitos = telefone.replace(/\D/g, "");
// Remove código país se presente
const semCodigoPais = digitos.startsWith("55") && digitos.length > 11
? digitos.substring(2)
: digitos;
if (semCodigoPais.length === 11) {
// Celular (DDD + 9 + 8 dígitos)
const nonoDigito = semCodigoPais.charAt(2);
if (nonoDigito !== "9") return { valido: false };
return { valido: true, tipo: "celular", normalizado: semCodigoPais };
}
if (semCodigoPais.length === 10) {
// Fixo (DDD + 8 dígitos)
return { valido: true, tipo: "fixo", normalizado: semCodigoPais };
}
return { valido: false };
}
// Uso
validarTelefoneBR("(11) 98765-4321"); // { valido: true, tipo: "celular" }
validarTelefoneBR("(11) 3456-7890"); // { valido: true, tipo: "fixo" }
validarTelefoneBR("123"); // { valido: false }Máscara dinâmica (muda de 10 para 11 dígitos)
import { IMaskInput } from "react-imask";
<IMaskInput
mask={[
{ mask: "(00) 0000-0000" }, // fixo
{ mask: "(00) 00000-0000" }, // celular
]}
value={value}
onAccept={setValue}
placeholder="(00) 00000-0000"
/>PIS/PASEP: 11 dígitos com módulo 11
export function validarPIS(pis: string): boolean {
const digitos = pis.replace(/\D/g, "");
if (digitos.length !== 11) return false;
if (/^(\d)\1{10}$/.test(digitos)) return false;
const pesos = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
let sum = 0;
for (let i = 0; i < 10; i++) {
sum += parseInt(digitos[i]) * pesos[i];
}
let d = 11 - (sum % 11);
if (d >= 10) d = 0;
return d === parseInt(digitos[10]);
}RG: o documento que NÃO tem validação universal
RG é emitido por cada estado com formato diferente. Não há algoritmo universal de validação. Estratégia comum:
- Regex básico (aceita letras e números):
/^[A-Z0-9.-]{5,}$/i - Não valide dígito verificador (cada estado usa lógica diferente).
- Confie em validação backend pelo próprio sistema do cliente.
Contexto
Biblioteca completa: brazilian-values
npm install brazilian-valuesimport {
isCPF,
isCNPJ,
isCEP,
isPIS,
formatToCPF,
formatToCNPJ,
formatToPhone,
} from "brazilian-values";
// Validação
isCPF("123.456.789-09"); // true/false
isCNPJ("12.345.678/0001-90"); // true/false
isCEP("01001-000"); // true/false
isPIS("12345678901"); // true/false
// Formatação
formatToCPF("12345678909"); // "123.456.789-09"
formatToCNPJ("12345678000190"); // "12.345.678/0001-90"
formatToPhone("11987654321"); // "(11) 98765-4321"Dica
brazilian-valueseconomiza tempo e evita bugs sutis. Pacote pequeno (~20 KB), validado, mantido. Alternativas: cpf-cnpj-validator (foca em CPF/CNPJ), @fnando/cpf(minimalista).Backend: validação via Zod
import { z } from "zod";
import { isCPF, isCNPJ, isCEP } from "brazilian-values";
const formSchema = z.object({
nome: z.string().min(2),
email: z.string().email(),
cpf: z
.string()
.transform((s) => s.replace(/\D/g, ""))
.refine((s) => s.length === 11, { message: "CPF deve ter 11 dígitos" })
.refine(isCPF, { message: "CPF inválido" }),
cnpj: z
.string()
.optional()
.refine((s) => !s || isCNPJ(s), { message: "CNPJ inválido" }),
cep: z
.string()
.refine(isCEP, { message: "CEP inválido" }),
telefone: z
.string()
.refine((s) => validarTelefoneBR(s).valido, { message: "Telefone inválido" }),
});
// No handler da rota
export async function POST(req: Request) {
const body = await req.json();
const result = formSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ errors: result.error.flatten() },
{ status: 400 },
);
}
// Dados validados em result.data
await salvarNoBanco(result.data);
return Response.json({ ok: true });
}UX em formulários brasileiros
Feedback em tempo real vs onBlur
- Validação enquanto digita (errada): mostra “inválido” com 3 dígitos. Frustra.
- Validação onBlur (recomendada): usuário termina de digitar, sai do campo, então valida. Experiência natural.
- Validação no submit: muito tarde — usuário já preencheu tudo. Use como fallback, não como principal.
Ordem dos campos
Padrão brasileiro:
- Nome
- CPF (ou CNPJ)
- Telefone (celular primeiro, fixo depois se houver)
- CEP (que preenche endereço)
- Rua, número, complemento (autocompletados pelo CEP)
- Bairro, cidade, UF (autocompletados)
Mensagens de erro claras
- Bom: “CPF inválido. Verifique os dígitos.”
- Ruim: “Erro”, “Dado inválido”, “Regex falhou”.
Inputmode para teclado mobile correto
<input type="tel" inputMode="numeric" /> // CPF, CNPJ, telefone
<input type="email" inputMode="email" /> // e-mail
<input type="text" inputMode="text" /> // nome, endereçoNo mobile, inputMode=“numeric” mostra teclado numérico direto — economiza 2 toques por campo, multiplica por 5 campos = menos atrito.
Armazenando corretamente no banco
| Critério | Formato recomendado | Exemplo |
|---|---|---|
| CPF | 11 dígitos (string) | "12345678909" |
| CNPJ | 14 dígitos (string) | "12345678000190" |
| CEP | 8 dígitos (string) | "01001000" |
| Telefone | +5511987654321 (E.164) | "+5511987654321" |
| PIS | 11 dígitos (string) | "12345678901" |
Em PostgreSQL, use VARCHAR(11) para CPF e VARCHAR(14) para CNPJ. Evite BIGINT — CPF começando com zero perde o zero.
Erros clássicos que quebram validação
- Não normalizar antes de validar: “123.456.789-09” com regex de 11 dígitos falha.
- Não rejeitar sequências repetidas:
000.000.000-00passa no módulo 11 ingenuamente implementado. - Validar apenas no frontend: vulnerável a ataques diretos.
- Armazenar com formatação: duplica armazenamento, dificulta queries.
- Regex de telefone que exige formatação: quebra quando usuário cola apenas dígitos.
- Não aceitar DDD sem parênteses: frustra usuário.
- Preenchimento de endereço que não permite edição: bug clássico; usuário precisa editar quando CEP retorna dado incorreto.
- CPF obrigatório para estrangeiros: considere opção de passaporte ou CPF alfanumérico (novo padrão).
Integração end-to-end: componente React completo
"use client";
import { useState } from "react";
import { IMaskInput } from "react-imask";
import { isCPF, isCEP, formatToCPF } from "brazilian-values";
import { buscarCEP } from "@/lib/via-cep";
export function FormularioCadastro() {
const [formData, setFormData] = useState({
nome: "",
email: "",
cpf: "",
cep: "",
endereco: "",
numero: "",
cidade: "",
uf: "",
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [submitting, setSubmitting] = useState(false);
const validateField = (field: string, value: string) => {
const newErrors = { ...errors };
delete newErrors[field];
if (field === "cpf") {
if (value && !isCPF(value)) {
newErrors.cpf = "CPF inválido";
}
}
if (field === "cep") {
if (value && !isCEP(value)) {
newErrors.cep = "CEP inválido";
}
}
if (field === "email") {
if (value && !/^[\w.+-]+@[\w-]+\.[\w-]+$/.test(value)) {
newErrors.email = "E-mail inválido";
}
}
setErrors(newErrors);
};
const handleCepBlur = async () => {
validateField("cep", formData.cep);
if (!isCEP(formData.cep)) return;
const endereco = await buscarCEP(formData.cep);
if (endereco) {
setFormData((prev) => ({
...prev,
endereco: endereco.logradouro,
cidade: endereco.localidade,
uf: endereco.uf,
}));
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitting(true);
const res = await fetch("/api/cadastrar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
if (!res.ok) {
const { errors: serverErrors } = await res.json();
setErrors(serverErrors.fieldErrors);
}
setSubmitting(false);
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Nome completo"
value={formData.nome}
onChange={(e) => setFormData({ ...formData, nome: e.target.value })}
/>
<input
type="email"
inputMode="email"
placeholder="E-mail"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
onBlur={() => validateField("email", formData.email)}
aria-invalid={!!errors.email}
/>
{errors.email && <span>{errors.email}</span>}
<IMaskInput
mask="000.000.000-00"
value={formData.cpf}
onAccept={(val: string) => setFormData({ ...formData, cpf: val })}
onBlur={() => validateField("cpf", formData.cpf)}
placeholder="CPF"
/>
{errors.cpf && <span>{errors.cpf}</span>}
<IMaskInput
mask="00000-000"
value={formData.cep}
onAccept={(val: string) => setFormData({ ...formData, cep: val })}
onBlur={handleCepBlur}
placeholder="CEP"
/>
{errors.cep && <span>{errors.cep}</span>}
<input
placeholder="Endereço"
value={formData.endereco}
onChange={(e) => setFormData({ ...formData, endereco: e.target.value })}
/>
<input
placeholder="Número"
value={formData.numero}
onChange={(e) => setFormData({ ...formData, numero: e.target.value })}
/>
<input
placeholder="Cidade"
value={formData.cidade}
onChange={(e) => setFormData({ ...formData, cidade: e.target.value })}
/>
<input
placeholder="UF"
maxLength={2}
value={formData.uf}
onChange={(e) => setFormData({ ...formData, uf: e.target.value.toUpperCase() })}
/>
<button type="submit" disabled={submitting || Object.keys(errors).length > 0}>
{submitting ? "Enviando..." : "Cadastrar"}
</button>
</form>
);
}Acessibilidade em formulários brasileiros
- Label sempre associado ao input:
<label htmlFor>+<input id>. - Erros acessíveis:
aria-invalid=“true”+aria-describedbyapontando para o span de erro. - Não dependa apenas de cor: ícone X ao lado do campo, texto claro.
- Contraste de erro: vermelho WCAG AA (4.5:1).
- Botão submit não desabilita sem feedback: mostre motivo (“Complete os campos obrigatórios”).
Segurança em formulários
- Rate limit: evita ataques de cadastro em massa.
- CAPTCHA em formulários públicos: reCAPTCHA v3 (invisível) ou Cloudflare Turnstile.
- CSRF token: obrigatório em formulários autenticados.
- HTTPS: dados pessoais sempre via conexão segura.
- Sanitize HTML no backend: prevê XSS armazenado.
- Normalize e valide server-side: regra de ouro.
Validação em uma frase
Validação de documentos brasileiros em formulários exige conhecimento específico — módulo 11 para CPF e CNPJ, estrutura de DDD + 9 para telefone celular, integração com ViaCEP para endereço. Combinado com máscara amigável, validação dupla (frontend + backend), normalização consistente e mensagens de erro claras, o resultado é formulário que converte bem e não frustra usuário.
Perguntas frequentes
Preciso validar documentos no frontend e no backend?+
Nos dois. Frontend dá feedback imediato ao usuário (UX), backend é a validação confiável (segurança). Cliente pode desabilitar JavaScript, manipular DOM ou enviar request via curl direto. Nunca confie só em validação client-side para regras críticas como CPF ou CNPJ — ataques direcionados passam por validação frontend sem dificuldade.
Máscara de input melhora ou atrapalha a experiência?+
Melhora, quando bem feita. Máscara formata enquanto usuário digita (000.000.000-00 aparece sozinho), reduz erro e acelera preenchimento. Mal feita (bloqueia colar de clipboard, não permite correção) frustra. Bibliotecas como IMask e React Input Mask resolvem a maioria dos casos. Sempre deixe o usuário colar valor formatado ou sem formato — sua máscara se ajusta.
Qual regex para CPF, CNPJ e CEP?+
CPF: /^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$/. CNPJ: /^\d{2}\.?\d{3}\.?\d{3}\/?\d{4}-?\d{2}$/. CEP: /^\d{5}-?\d{3}$/. Telefone celular: /^\(?\d{2}\)?\s?9?\d{4,5}-?\d{4}$/. Importante: regex valida formato, mas CPF e CNPJ também precisam validar dígito verificador via algoritmo de módulo 11 — regex sozinha aceita 000.000.000-00, que é inválido na Receita Federal.
Como validar telefone brasileiro que aceita fixo e celular?+
Celular tem 11 dígitos (incluindo DDD), começando com 9 no nono dígito. Fixo tem 10 dígitos (DDD + 4 dígitos iniciais + 4 finais). Regex genérica: /^\(?\d{2}\)?\s?9?\d{4,5}-?\d{4}$/. Para validação mais rigorosa, verifique após o DDD: se o nono dígito é 9, é celular; se não, é fixo. Use bibliotecas como libphonenumber-js para validação internacional que inclui BR.
Devo permitir que o usuário digite sem formatação?+
Sim. O usuário pode colar CPF “12345678909” (sem ponto nem hífen) ou “123.456.789-09”. Normalize no backend antes de validar: remova tudo que não é dígito (.replace(/\D/g, '')). Ao exibir, reaplique formatação. Armazenar no banco: apenas os 11 dígitos do CPF (ou 14 do CNPJ) — economiza 20-30% de espaço e elimina ambiguidade.
Como integrar busca de CEP com preenchimento automático de endereço?+
API pública dos Correios via ViaCEP (viacep.com.br/ws/01001000/json) retorna logradouro, bairro, cidade, UF a partir do CEP. Implementação: usuário digita CEP completo (8 dígitos), onBlur dispara fetch à API, preenche automaticamente os campos de endereço. Reduz digitação em 60-80%. Alternativas: AwesomeAPI, BrasilAPI — todos grátis e com SLA razoável.
PIS, CPF e CNPJ compartilham algoritmo de validação?+
Parcialmente. Todos usam módulo 11 para calcular dígito verificador, mas com pesos diferentes: CPF tem 9 dígitos + 2 verificadores; CNPJ tem 12 + 2; PIS/PASEP tem 10 + 1. Biblioteca como brazilian-values oferece validadores prontos para os três. Implementar cada um individualmente requer conhecer os pesos específicos — fácil de errar em código próprio.
O que fazer quando o usuário digita CPF inválido no mobile?+
Feedback contextual. Mostre erro apenas onBlur (após sair do campo), não em tempo real enquanto digita — senão mostra “inválido” com 3 dígitos. Mensagem específica: “CPF inválido” ou “Verifique os números digitados”. Ícone visual (X vermelho) junto com a mensagem ajuda. Para acessibilidade, use aria-invalid=“true” e aria-describedby apontando para o span de erro.
Artigos relacionados
Como Validar CPF e CNPJ (2026): Algoritmo, Código em JavaScript e Boas Práticas
Guia completo de validação de CPF e CNPJ: algoritmo do módulo 11 passo a passo, implementação em JavaScript, máscara em formulários, erros clássicos e LGPD.
Como Gerar CPF Válido em JavaScript: Algoritmo Passo a Passo (2026)
Implementação completa do algoritmo gerador de CPF em JavaScript: módulo 11 dos dígitos verificadores, geração por estado, batch, função TypeScript pronta, comparação com bibliotecas e LGPD em testes.
Algoritmo do Dígito Verificador do CPF: Módulo 11 Completo (2026)
Como funciona o algoritmo de módulo 11 do CPF passo a passo. Implementações em JavaScript, Python, Go, Java e SQL. Casos especiais, performance e o que muda com o novo CPF alfanumérico.
Regex para Validação de Formulários (2026): E-mail, CPF, Telefone, CEP
Coleção de regex prontas para campos brasileiros: e-mail, CPF, CNPJ, telefone, CEP, URL, data, senha forte, cartão. Inclui máscara em tempo real, integração React e ReDoS.