Experiência do usuário ao digitar CPF errado: como guiar sem frustrar

Estratégias de UX para lidar com CPFs digitados incorretamente, guiando o usuário sem causar frustração no formulário.

Redação CPFHub.io
Redação CPFHub.io
··10 min de leitura
Experiência do usuário ao digitar CPF errado: como guiar sem frustrar

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.

Redação CPFHub.io

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.

WhatsAppFale conosco via WhatsApp