Para exibir feedback visual de validação de CPF, combine cinco estados distintos — neutro, digitando, validando, válido e inválido — com cores semânticas, ícones inline e mensagens orientadoras. A abordagem correta nunca depende só da cor: sempre emparelha ícone + texto para garantir acessibilidade a usuários com daltonismo.
Introdução
Quando um usuário digita seu CPF em um formulário, ele precisa saber rapidamente se o dado foi aceito, rejeitado ou está sendo processado. Feedback visual claro e imediato é a diferença entre uma experiência fluida e uma experiência frustrante. Sem feedback, o usuário fica na dúvida; com feedback ruim, ele fica confuso.
Cada exemplo neste guia é implementado com HTML, CSS e JavaScript, com integração à API do CPFHub.io para validação em tempo real.
Os cinco estados de um campo de CPF
Um campo de CPF bem projetado deve representar visualmente cinco estados:
- Neutro: o campo está vazio ou com dados parciais.
- Digitando: o usuário está preenchendo o campo.
- Validando: a requisição à API está em andamento.
- Válido: o CPF foi confirmado pela API.
- Inválido: o CPF não foi encontrado ou tem formato incorreto.
Cada estado precisa de uma representação visual distinta usando cor, ícone e texto.
Sistema de cores para feedback
Escolha acessível de cores
As cores devem funcionar para pessoas com daltonismo. Nunca dependa exclusivamente da cor para comunicar o estado — sempre combine com texto e ícones:
:root {
/* Estado neutro */
--cpf-neutral-border: #d1d5db;
--cpf-neutral-bg: #ffffff;
/* Digitando */
--cpf-focus-border: #3b82f6;
--cpf-focus-shadow: rgba(59, 130, 246, 0.15);
/* Validando */
--cpf-loading-border: #f59e0b;
--cpf-loading-text: #92400e;
/* Válido */
--cpf-valid-border: #059669;
--cpf-valid-bg: #ecfdf5;
--cpf-valid-text: #065f46;
/* Inválido */
--cpf-invalid-border: #dc2626;
--cpf-invalid-bg: #fef2f2;
--cpf-invalid-text: #991b1b;
}
.cpf-input {
width: 100%;
padding: 12px 44px 12px 16px;
border: 2px solid var(--cpf-neutral-border);
border-radius: 8px;
font-size: 16px;
transition: border-color 0.2s ease, box-shadow 0.2s ease,
background-color 0.2s ease;
outline: none;
}
.cpf-input:focus {
border-color: var(--cpf-focus-border);
box-shadow: 0 0 0 4px var(--cpf-focus-shadow);
}
.cpf-input--loading {
border-color: var(--cpf-loading-border);
}
.cpf-input--valid {
border-color: var(--cpf-valid-border);
background-color: var(--cpf-valid-bg);
}
.cpf-input--invalid {
border-color: var(--cpf-invalid-border);
background-color: var(--cpf-invalid-bg);
}
Ícones inline no campo
Ícones posicionados dentro do campo de input são o feedback visual mais rápido de ser percebido:
<div class="cpf-wrapper">
<input
type="text"
id="cpf"
class="cpf-input"
placeholder="000.000.000-00"
inputmode="numeric"
maxlength="14"
/>
<div class="cpf-icon" id="cpf-icon" aria-hidden="true"></div>
</div>
<div class="cpf-message" id="cpf-message" role="status" aria-live="polite"></div>
.cpf-wrapper {
position: relative;
}
.cpf-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
/* Spinner de loading */
.cpf-icon--loading::after {
content: "";
width: 18px;
height: 18px;
border: 2px solid var(--cpf-loading-border);
border-top-color: transparent;
border-radius: 50%;
animation: cpf-spin 0.6s linear infinite;
}
/* Checkmark de sucesso */
.cpf-icon--valid svg {
width: 20px;
height: 20px;
color: var(--cpf-valid-border);
}
/* X de erro */
.cpf-icon--invalid svg {
width: 20px;
height: 20px;
color: var(--cpf-invalid-border);
}
@keyframes cpf-spin {
to {
transform: rotate(360deg);
}
}
Implementação JavaScript completa
var cpfInput = document.getElementById("cpf");
var cpfIcon = document.getElementById("cpf-icon");
var cpfMessage = document.getElementById("cpf-message");
var debounceTimer = null;
var ICONS = {
loading: "",
valid:
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M5 13l4 4L19 7"/></svg>',
invalid:
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>',
};
cpfInput.addEventListener("input", function () {
formatCpf(this);
var digits = this.value.replace(/\D/g, "");
if (digits.length < 11) {
resetState();
return;
}
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
validateCpf(digits);
}, 500);
});
function formatCpf(field) {
var v = field.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");
field.value = v;
}
function resetState() {
cpfInput.className = "cpf-input";
cpfIcon.className = "cpf-icon";
cpfIcon.innerHTML = "";
cpfMessage.textContent = "";
cpfMessage.className = "cpf-message";
}
function setState(state, message) {
cpfInput.className = "cpf-input cpf-input--" + state;
cpfIcon.className = "cpf-icon cpf-icon--" + state;
cpfIcon.innerHTML = ICONS[state] || "";
cpfMessage.textContent = message;
cpfMessage.className = "cpf-message cpf-message--" + state;
}
async function validateCpf(cpf) {
setState("loading", "Validando CPF...");
var controller = new AbortController();
var timeoutId = setTimeout(function () {
controller.abort();
}, 10000);
try {
var response = 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 response.json();
if (data.success) {
setState("valid", "CPF válido - " + data.data.name);
} else {
setState("invalid", "CPF não encontrado na base de dados.");
}
} catch (err) {
clearTimeout(timeoutId);
setState(
"invalid",
err.name === "AbortError"
? "Tempo de validação excedido."
: "Erro ao validar. Tente novamente."
);
}
}
Mensagens textuais claras
O texto do feedback é tão importante quanto a cor e o ícone. Boas mensagens são:
- Específicas: dizem exatamente o que aconteceu.
- Orientadoras: indicam o que o usuário deve fazer.
- Humanas: evitam jargão técnico.
Exemplos de mensagens
| Estado | Mensagem ruim | Mensagem boa |
|---|---|---|
| Formato inválido | "Erro 422" | "CPF deve ter 11 dígitos. Verifique o número digitado." |
| Validando | "Aguarde..." | "Estamos validando seu CPF..." |
| Válido | "OK" | "CPF válido — Nome: João Silva" |
| Não encontrado | "Falha" | "CPF não encontrado. Verifique se digitou corretamente." |
| Erro de rede | "Error" | "Não foi possível validar agora. Tente novamente em instantes." |
Estilos para mensagens
.cpf-message {
font-size: 13px;
margin-top: 6px;
min-height: 20px;
display: flex;
align-items: center;
gap: 4px;
opacity: 0;
transform: translateY(-4px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.cpf-message--loading,
.cpf-message--valid,
.cpf-message--invalid {
opacity: 1;
transform: translateY(0);
}
.cpf-message--loading {
color: var(--cpf-loading-text);
}
.cpf-message--valid {
color: var(--cpf-valid-text);
}
.cpf-message--invalid {
color: var(--cpf-invalid-text);
}
Animação de checkmark SVG
Uma animação de checkmark desenhado é mais satisfatória do que simplesmente mostrar um ícone estático:
.cpf-icon--valid svg path {
stroke-dasharray: 24;
stroke-dashoffset: 24;
animation: draw-check 0.4s ease forwards 0.1s;
}
@keyframes draw-check {
to {
stroke-dashoffset: 0;
}
}
Implementação em React
Para projetos React, encapsule toda a lógica em um componente:
import React, { useState, useRef } from "react";
function CpfInput({ onValidated }) {
const [value, setValue] = useState("");
const [state, setState] = useState("neutral");
const [message, setMessage] = useState("");
const debounceRef = useRef(null);
function formatCpf(raw) {
var v = raw.replace(/\D/g, "").slice(0, 11);
if (v.length > 9)
return v.replace(/(\d{3})(\d{3})(\d{3})(\d{1,2})/, "$1.$2.$3-$4");
if (v.length > 6)
return v.replace(/(\d{3})(\d{3})(\d{1,3})/, "$1.$2.$3");
if (v.length > 3) return v.replace(/(\d{3})(\d{1,3})/, "$1.$2");
return v;
}
async function validate(cpf) {
setState("loading");
setMessage("Validando CPF...");
const ctrl = new AbortController();
const tid = setTimeout(() => ctrl.abort(), 10000);
try {
const res = await fetch("https://api.cpfhub.io/cpf/" + cpf, {
headers: {
"x-api-key": "SUA_CHAVE_DE_API",
Accept: "application/json",
},
signal: ctrl.signal,
});
clearTimeout(tid);
const data = await res.json();
if (data.success) {
setState("valid");
setMessage("CPF válido - " + data.data.name);
if (onValidated) onValidated(data.data);
} else {
setState("invalid");
setMessage("CPF não encontrado.");
}
} catch {
clearTimeout(tid);
setState("invalid");
setMessage("Erro na validação.");
}
}
function handleChange(e) {
const formatted = formatCpf(e.target.value);
setValue(formatted);
const digits = formatted.replace(/\D/g, "");
if (digits.length < 11) {
setState("neutral");
setMessage("");
return;
}
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => validate(digits), 500);
}
return (
<div className="cpf-field">
<div className="cpf-wrapper">
<input
type="text"
className={`cpf-input cpf-input--${state}`}
value={value}
onChange={handleChange}
placeholder="000.000.000-00"
inputMode="numeric"
maxLength={14}
/>
<div className={`cpf-icon cpf-icon--${state}`} aria-hidden="true" />
</div>
{message && (
<div className={`cpf-message cpf-message--${state}`} role="status">
{message}
</div>
)}
</div>
);
}
export default CpfInput;
Acessibilidade
Feedback visual deve ser acompanhado de feedback acessível:
- Use
role="status"earia-live="polite"para mensagens. - Nunca comunique estados apenas por cor.
- Mantenha contraste mínimo de 4.5:1 para texto.
- Respeite
prefers-reduced-motionpara animações.
@media (prefers-reduced-motion: reduce) {
.cpf-input,
.cpf-message,
.cpf-icon--loading::after {
transition: none;
animation: none;
}
}
Perguntas frequentes
Qual é o tempo de resposta da API de CPF para o estado "validando" no frontend?
A API da CPFHub.io responde em aproximadamente 900ms. Para o usuário, esse intervalo deve ser preenchido com um spinner animado e a mensagem "Estamos validando seu CPF...". Use debounce de 300–500ms após o último dígito digitado para evitar chamadas desnecessárias enquanto o usuário ainda está digitando.
Como garantir que o feedback de erro seja acessível para usuários com daltonismo?
Nunca dependa exclusivamente da cor para comunicar o estado. Combine sempre cor + ícone + texto: o estado inválido deve mostrar borda vermelha, ícone X e a mensagem "CPF não encontrado. Verifique se digitou corretamente." Use aria-live="polite" para que leitores de tela anunciem a mudança de estado automaticamente.
A API bloqueia o frontend quando o limite de consultas é atingido?
Não. A API da CPFHub.io nunca interrompe o serviço — quando o limite mensal é atingido, ela continua respondendo e cobra R$0,15 por consulta adicional. O frontend continua exibindo feedback normalmente, sem erros inesperados de bloqueio. A ANPD recomenda que o tratamento de dados seja informado ao usuário antes da consulta.
Como implementar o feedback visual em formulários com múltiplos campos de CPF?
Encapsule a lógica em uma função ou componente reutilizável que receba o elemento de input como parâmetro. Cada instância gerencia seu próprio estado e timer de debounce independentemente. No React, o componente CpfInput acima já é reutilizável por padrão — basta instanciá-lo múltiplas vezes com diferentes callbacks onValidated.
Conclusão
Feedback visual bem implementado transforma a validação de CPF de um ponto de atrito em uma experiência satisfatória. A combinação de cores semânticas, ícones claros, animações sutis e mensagens orientadoras cria um campo de CPF que o usuário confia e completa sem hesitar.
Com respostas em ~900ms e conformidade com a LGPD, a CPFHub.io fornece a base confiável para um feedback visual preciso e imediato.
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.


