Quando um usuário digita o CPF errado, a forma como o formulário responde define se ele corrige e continua ou abandona o processo. Mensagens genéricas como "CPF inválido" aumentam a taxa de abandono — o correto é identificar o tipo de erro (dígitos incompletos, verificadores incorretos, sequência repetida) e orientar com uma instrução específica. A ANPD orienta que formulários de coleta de dados pessoais sejam claros e transparentes com o titular — o que inclui explicar por que um dado foi rejeitado.
Introdução
Erros ao digitar o CPF são mais comuns do que se imagina. Dedos escorregam, números se invertem, dígitos são esquecidos. Quando isso acontece, a forma como o formulário responde define se o usuário corrige o erro rapidamente ou abandona o processo frustrado. Uma mensagem genérica de "CPF inválido" não ajuda — o usuário precisa saber exatamente o que está errado e como corrigir.
Cada técnica apresentada neste guia é demonstrada com código frontend e, quando relevante, integração com a API do CPFHub.io para validação em tempo real.
Tipos de erro ao digitar CPF
Antes de projetar o feedback, é importante entender os tipos de erro:
1. Dígitos incompletos
O usuário digitou menos de 11 dígitos. Causa mais comum: distração ou confusão com a formatação.
2. Dígitos verificadores incorretos
Os dois últimos dígitos não correspondem ao algoritmo. Causa: erro de digitação em um ou mais números.
3. Sequência repetida
Todos os dígitos são iguais (111.111.111-11). Causa: tentativa de burlar o formulário ou teste.
4. CPF não encontrado
O formato é válido, mas o CPF não existe na base de dados. Causa: CPF fictício ou erro sutil na digitação.
5. CPF de terceiro
O usuário digitou o CPF de outra pessoa por engano. Causa: CPFs salvos confundidos.
Mensagens de erro específicas por tipo
A primeira regra é nunca usar uma mensagem genérica. Identifique o tipo de erro e responda de forma específica:
function getErrorMessage(cpf) {
var digits = cpf.replace(/\D/g, "");
if (digits.length === 0) {
return {
type: "empty",
message: "Por favor, digite seu CPF.",
hint: "O CPF tem 11 dígitos numéricos.",
};
}
if (digits.length < 11) {
var missing = 11 - digits.length;
return {
type: "incomplete",
message:
"O CPF está incompleto. " +
(missing === 1
? "Falta 1 dígito."
: "Faltam " + missing + " dígitos."),
hint: "O CPF completo tem 11 dígitos: 000.000.000-00",
};
}
if (digits.length > 11) {
return {
type: "excess",
message: "O CPF tem dígitos a mais.",
hint: "Remova " + (digits.length - 11) + " dígito(s) excedente(s).",
};
}
if (/^(\d)\1{10}$/.test(digits)) {
return {
type: "repeated",
message: "CPF com todos os dígitos iguais não é válido.",
hint: "Verifique se digitou o número correto.",
};
}
if (!validateCheckDigits(digits)) {
return {
type: "check_digits",
message: "Os dígitos verificadores do CPF não conferem.",
hint: "Pode ter havido um erro de digitação. Confira os números.",
};
}
return null; // CPF localmente válido
}
function validateCheckDigits(digits) {
var sum = 0;
for (var i = 0; i < 9; i++) sum += parseInt(digits[i]) * (10 - i);
var r1 = (sum * 10) % 11;
if (r1 === 10) r1 = 0;
if (r1 !== parseInt(digits[9])) return false;
sum = 0;
for (var i = 0; i < 10; i++) sum += parseInt(digits[i]) * (11 - i);
var r2 = (sum * 10) % 11;
if (r2 === 10) r2 = 0;
return r2 === parseInt(digits[10]);
}
Feedback visual por tipo de erro
Cada tipo de erro deve ter uma representação visual distinta:
<div class="cpf-field" id="cpf-field">
<label for="cpf">CPF</label>
<div class="cpf-input-wrapper">
<input
type="text"
id="cpf"
class="cpf-input"
inputmode="numeric"
maxlength="14"
placeholder="000.000.000-00"
/>
<span class="cpf-counter" id="cpf-counter">0/11</span>
</div>
<div class="cpf-feedback" id="cpf-feedback" role="alert" aria-live="assertive">
<span class="cpf-feedback__message" id="cpf-message"></span>
<span class="cpf-feedback__hint" id="cpf-hint"></span>
</div>
</div>
.cpf-input-wrapper {
position: relative;
}
.cpf-counter {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
color: #94a3b8;
font-family: monospace;
transition: color 0.2s ease;
}
.cpf-counter--complete {
color: #059669;
font-weight: 600;
}
.cpf-counter--excess {
color: #dc2626;
font-weight: 600;
}
.cpf-feedback {
margin-top: 6px;
opacity: 0;
max-height: 0;
overflow: hidden;
transition: opacity 0.2s ease, max-height 0.3s ease;
}
.cpf-feedback--visible {
opacity: 1;
max-height: 60px;
}
.cpf-feedback__message {
display: block;
font-size: 13px;
font-weight: 500;
}
.cpf-feedback__hint {
display: block;
font-size: 12px;
color: #64748b;
margin-top: 2px;
}
/* Cores por tipo */
.cpf-field--incomplete .cpf-input {
border-color: #f59e0b;
}
.cpf-field--incomplete .cpf-feedback__message {
color: #92400e;
}
.cpf-field--check_digits .cpf-input,
.cpf-field--repeated .cpf-input,
.cpf-field--excess .cpf-input {
border-color: #dc2626;
}
.cpf-field--check_digits .cpf-feedback__message,
.cpf-field--repeated .cpf-feedback__message,
.cpf-field--excess .cpf-feedback__message {
color: #991b1b;
}
.cpf-field--not_found .cpf-input {
border-color: #f59e0b;
}
.cpf-field--not_found .cpf-feedback__message {
color: #92400e;
}
.cpf-field--valid .cpf-input {
border-color: #059669;
background-color: #ecfdf5;
}
.cpf-field--valid .cpf-feedback__message {
color: #065f46;
}
Implementação completa do JavaScript
var cpfInput = document.getElementById("cpf");
var cpfField = document.getElementById("cpf-field");
var counter = document.getElementById("cpf-counter");
var messageEl = document.getElementById("cpf-message");
var hintEl = document.getElementById("cpf-hint");
var feedbackEl = document.getElementById("cpf-feedback");
var debounceTimer = null;
cpfInput.addEventListener("input", function () {
// Formatar
var v = this.value.replace(/\D/g, "").slice(0, 11);
if (v.length > 9)
v = v.replace(/(\d{3})(\d{3})(\d{3})(\d{1,2})/, "$1.$2.$3-$4");
else if (v.length > 6)
v = v.replace(/(\d{3})(\d{3})(\d{1,3})/, "$1.$2.$3");
else if (v.length > 3) v = v.replace(/(\d{3})(\d{1,3})/, "$1.$2");
this.value = v;
var digits = v.replace(/\D/g, "");
// Atualizar contador
counter.textContent = digits.length + "/11";
counter.className =
"cpf-counter" +
(digits.length === 11
? " cpf-counter--complete"
: digits.length > 11
? " cpf-counter--excess"
: "");
// Validação local
if (digits.length < 11) {
if (digits.length > 0) {
showLocalFeedback(digits);
} else {
hideFeedback();
}
return;
}
// Validação local completa
var localError = getErrorMessage(this.value);
if (localError) {
showError(localError);
return;
}
// Validação via API com debounce
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
validateWithAPI(digits);
}, 400);
});
function showLocalFeedback(digits) {
var missing = 11 - digits.length;
cpfField.className = "cpf-field cpf-field--incomplete";
messageEl.textContent =
missing === 1 ? "Falta 1 dígito" : "Faltam " + missing + " dígitos";
hintEl.textContent = "";
feedbackEl.className = "cpf-feedback cpf-feedback--visible";
}
function showError(error) {
cpfField.className = "cpf-field cpf-field--" + error.type;
messageEl.textContent = error.message;
hintEl.textContent = error.hint || "";
feedbackEl.className = "cpf-feedback cpf-feedback--visible";
// Shake sutil no input
cpfInput.classList.add("cpf-input--shake");
setTimeout(function () {
cpfInput.classList.remove("cpf-input--shake");
}, 400);
}
function hideFeedback() {
cpfField.className = "cpf-field";
feedbackEl.className = "cpf-feedback";
}
async function validateWithAPI(cpf) {
cpfField.className = "cpf-field cpf-field--loading";
messageEl.textContent = "Verificando CPF...";
hintEl.textContent = "";
feedbackEl.className = "cpf-feedback cpf-feedback--visible";
var controller = new AbortController();
var timeoutId = setTimeout(function () {
controller.abort();
}, 10000);
try {
var res = await fetch("https://api.cpfhub.io/cpf/" + cpf, {
headers: {
"x-api-key": "SUA_CHAVE_DE_API",
Accept: "application/json",
},
signal: controller.signal,
});
clearTimeout(timeoutId);
var data = await res.json();
if (data.success) {
cpfField.className = "cpf-field cpf-field--valid";
messageEl.textContent = "CPF válido - " + data.data.name;
hintEl.textContent = "";
} else {
cpfField.className = "cpf-field cpf-field--not_found";
messageEl.textContent = "CPF não localizado na base de dados.";
hintEl.textContent =
"Verifique se todos os dígitos estão corretos e tente novamente.";
}
} catch (err) {
clearTimeout(timeoutId);
cpfField.className = "cpf-field cpf-field--check_digits";
messageEl.textContent = "Não foi possível verificar o CPF.";
hintEl.textContent = "Tente novamente em alguns instantes.";
}
}
.cpf-input--shake {
animation: shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-3px); }
40%, 80% { transform: translateX(3px); }
}
Sugerindo correções
Quando o erro é nos dígitos verificadores, o sistema pode sugerir qual dígito provavelmente está errado:
function suggestCorrection(digits) {
// Testar se alterar um único dígito gera um CPF válido
for (var pos = 0; pos < 11; pos++) {
for (var d = 0; d <= 9; d++) {
if (d === parseInt(digits[pos])) continue;
var candidate =
digits.slice(0, pos) + d.toString() + digits.slice(pos + 1);
if (validateCheckDigits(candidate) && !/^(\d)\1{10}$/.test(candidate)) {
var formatted = candidate.replace(
/(\d{3})(\d{3})(\d{3})(\d{2})/,
"$1.$2.$3-$4"
);
return {
position: pos + 1,
suggestion: formatted,
};
}
}
}
return null;
}
Exiba a sugestão de forma não intrusiva:
var correction = suggestCorrection(digits);
if (correction) {
hintEl.innerHTML =
'Possível erro no ' + correction.position + 'o dígito. ' +
'Você quis dizer <button class="cpf-suggestion" type="button">' +
correction.suggestion +
'</button>?';
hintEl.querySelector(".cpf-suggestion").addEventListener("click", function () {
cpfInput.value = correction.suggestion;
cpfInput.dispatchEvent(new Event("input", { bubbles: true }));
});
}
Prevenindo erros antes que aconteçam
Tamanho adequado do campo
O campo deve ter largura suficiente para exibir o CPF formatado (14 caracteres) sem scroll horizontal.
Teclado numérico no mobile
Use inputmode="numeric" para exibir o teclado numérico em dispositivos móveis.
Colar CPF com formatação
Permita colar CPFs formatados ou não:
cpfInput.addEventListener("paste", function (e) {
e.preventDefault();
var pasted = (e.clipboardData || window.clipboardData).getData("text");
var digits = pasted.replace(/\D/g, "").slice(0, 11);
this.value = digits;
this.dispatchEvent(new Event("input", { bubbles: true }));
});
Perguntas frequentes
Por que não basta mostrar "CPF inválido" como mensagem de erro?
Mensagens genéricas não dizem ao usuário o que fazer a seguir. "CPF inválido" pode significar dígitos faltando, dígitos verificadores errados, sequência repetida ou CPF não encontrado na base. Cada caso tem uma solução diferente: completar o número, conferir a digitação ou verificar o documento de origem. A mensagem específica reduz o atrito e aumenta a taxa de conclusão do formulário.
Qual é o momento certo para validar o CPF via API — no input ou no submit?
A melhor abordagem é em duas etapas: validação local (formato, dígitos verificadores) em tempo real enquanto o usuário digita, com debounce de 300–500ms; validação via API somente após o CPF local ser considerado válido. Isso evita chamadas desnecessárias à API e garante feedback instantâneo para os erros mais comuns.
Como lidar com o CPF de terceiro digitado por engano?
A validação via API retorna o nome do titular. Exibir "CPF válido — [nome]" permite que o próprio usuário perceba que o CPF não é o dele, sem expor dados de forma invasiva. Se a aplicação já tem o nome do usuário cadastrado, pode fazer a comparação automaticamente e alertar sobre a divergência.
A validação de CPF via API no frontend expõe a chave de API?
Sim, se chamada diretamente do navegador. O padrão recomendado é criar um endpoint intermediário no seu backend que recebe o CPF do frontend, consulta a API da CPFHub.io com a chave armazenada no servidor e retorna apenas o resultado necessário (válido/inválido e nome). Nunca exponha a chave de API no código JavaScript executado no cliente.
Conclusão
A experiência do usuário ao digitar um CPF errado define se ele corrige o erro e continua ou abandona o formulário frustrado. Mensagens de erro específicas, contadores de dígitos, sugestões de correção e feedback visual gradual transformam erros em oportunidades de orientação. Combinadas com a validação em tempo real da API do CPFHub.io — respostas em ~900ms, 99,9% de uptime e conformidade total com a LGPD — essas técnicas fornecem a base para um feedback preciso e instantâneo.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



