Implementar um input de CPF com máscara automática em React é mais simples do que parece: uma função pura que extrai dígitos e aplica o formato 000.000.000-00, combinada com validação de dígitos verificadores e uma chamada à API da CPFHub.io, resulta em um componente funcional com menos de 100 linhas de código e sem dependências externas.
Introdução
A máscara de input é um dos padrões de UX mais eficazes para campos de CPF. Ao formatar automaticamente os dígitos no padrão 000.000.000-00 enquanto o usuário digita, você reduz erros, melhora a legibilidade e transmite confiança de que o sistema entende o dado esperado.
Por que não usar bibliotecas de máscara prontas
Bibliotecas como react-input-mask e react-text-mask são populares, mas para um caso tão específico quanto CPF, existem boas razões para construir sua própria solução:
- Controle total sobre o comportamento de colagem, seleção e backspace.
- Bundle menor -- evitar dependências para um caso de uso simples.
- Personalização -- integrar validação de dígitos verificadores diretamente no componente.
- Manutenibilidade -- menos dependências para atualizar e auditar.
O componente que vamos criar terá menos de 100 linhas de código e cobrirá todos os edge cases.
Implementando a função de máscara
A função de máscara recebe uma string de dígitos e retorna o CPF formatado. O segredo é extrair apenas os dígitos do input e aplicar a formatação programaticamente.
function aplicarMascaraCPF(valor) {
const digits = valor.replace(/\D/g, '').slice(0, 11);
let resultado = '';
for (let i = 0; i < digits.length; i++) {
if (i === 3 || i === 6) resultado += '.';
if (i === 9) resultado += '-';
resultado += digits[i];
}
return resultado;
}
// Exemplos:
// aplicarMascaraCPF('123') => '123'
// aplicarMascaraCPF('12345678') => '123.456.78'
// aplicarMascaraCPF('12345678901') => '123.456.789-01'
Validação de dígitos verificadores
Antes de consultar a API, é fundamental validar os dígitos verificadores do CPF localmente. Essa validação elimina chamadas desnecessárias e dá feedback instantâneo ao usuário. A Receita Federal define o algoritmo oficial de validação dos dígitos verificadores do CPF.
function validarCPF(cpf) {
const digits = cpf.replace(/\D/g, '');
if (digits.length !== 11) return false;
if (/^(\d)\1{10}$/.test(digits)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) {
soma += parseInt(digits[i]) * (10 - i);
}
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(digits[9])) return false;
soma = 0;
for (let i = 0; i < 10; i++) {
soma += parseInt(digits[i]) * (11 - i);
}
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
return resto === parseInt(digits[10]);
}
Componente React completo
Agora vamos unir tudo em um componente React funcional com hooks. O componente gerencia o estado do input, aplica a máscara, válida o CPF e consulta a API.
import React, { useState, useCallback, useRef } from 'react';
function aplicarMascaraCPF(valor) {
const digits = valor.replace(/\D/g, '').slice(0, 11);
let resultado = '';
for (let i = 0; i < digits.length; i++) {
if (i === 3 || i === 6) resultado += '.';
if (i === 9) resultado += '-';
resultado += digits[i];
}
return resultado;
}
function validarCPF(cpf) {
const digits = cpf.replace(/\D/g, '');
if (digits.length !== 11) return false;
if (/^(\d)\1{10}$/.test(digits)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) soma += parseInt(digits[i]) * (10 - i);
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(digits[9])) return false;
soma = 0;
for (let i = 0; i < 10; i++) soma += parseInt(digits[i]) * (11 - i);
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
return resto === parseInt(digits[10]);
}
export default function CPFInput({ onVerified }) {
const [valor, setValor] = useState('');
const [status, setStatus] = useState('idle');
const [mensagem, setMensagem] = useState('');
const controllerRef = useRef(null);
const handleChange = useCallback((e) => {
const masked = aplicarMascaraCPF(e.target.value);
setValor(masked);
setStatus('idle');
setMensagem('');
}, []);
const handleBlur = useCallback(async () => {
const digits = valor.replace(/\D/g, '');
if (digits.length < 11) return;
if (!validarCPF(digits)) {
setStatus('error');
setMensagem('CPF invalido. Verifique os digitos.');
return;
}
setStatus('loading');
setMensagem('Consultando CPF...');
if (controllerRef.current) controllerRef.current.abort();
controllerRef.current = new AbortController();
const timeoutId = setTimeout(() => controllerRef.current.abort(), 10000);
try {
const response = await fetch(
`https://api.cpfhub.io/cpf/${digits}`,
{
method: 'GET',
headers: {
'x-api-key': process.env.REACT_APP_CPFHUB_API_KEY,
'Accept': 'application/json'
},
signal: controllerRef.current.signal
}
);
clearTimeout(timeoutId);
const data = await response.json();
if (data.success) {
setStatus('success');
setMensagem(`CPF verificado: ${data.data.name}`);
if (onVerified) onVerified(data.data);
} else {
setStatus('error');
setMensagem('CPF nao encontrado na base.');
}
} catch (err) {
clearTimeout(timeoutId);
setStatus('error');
setMensagem(
err.name === 'AbortError'
? 'Consulta expirou. Tente novamente.'
: 'Erro ao consultar. Tente novamente.'
);
}
}, [valor, onVerified]);
const borderColor =
status === 'success' ? '#2ecc71' :
status === 'error' ? '#e74c3c' :
status === 'loading' ? '#f39c12' : '#ccc';
return (
<div style={{ maxWidth: 360 }}>
<label htmlFor="cpf-input" style={{ display: 'block', marginBottom: 6 }}>
CPF
</label>
<input
id="cpf-input"
type="text"
inputMode="numeric"
placeholder="000.000.000-00"
value={valor}
onChange={handleChange}
onBlur={handleBlur}
maxLength={14}
autoComplete="off"
style={{
width: '100%',
padding: '12px 16px',
fontSize: '1.1rem',
border: `2px solid ${borderColor}`,
borderRadius: 8,
outline: 'none',
transition: 'border-color 0.2s'
}}
/>
{mensagem && (
<p style={{
marginTop: 8,
fontSize: '0.9rem',
color: status === 'success' ? '#2ecc71' : '#e74c3c'
}}>
{mensagem}
</p>
)}
</div>
);
}
Tratando edge cases
Colagem de CPF
Quando o usuário cola um CPF copiado de outro lugar, o valor pode vir em diferentes formatos: apenas dígitos, com pontos e traço, ou até com espaços. A função aplicarMascaraCPF já trata isso, pois extrai apenas os dígitos antes de formatar.
Cursor do input
Um problema comum em inputs com máscara é o cursor "pular" para o final do campo após cada digitação. Para resolver isso de forma simples, podemos usar inputMode="numeric", que em dispositivos móveis já apresenta o teclado numérico e minimiza o problema de posicionamento do cursor.
Acessibilidade
O componente deve incluir:
aria-labeloulabelassociado ao input viahtmlFor.aria-invalid="true"quando o CPF for inválido.aria-describedbyapontando para a mensagem de erro.
<input
id="cpf-input"
type="text"
inputMode="numeric"
aria-label="Numero do CPF"
aria-invalid={status === 'error'}
aria-describedby="cpf-feedback"
placeholder="000.000.000-00"
value={valor}
onChange={handleChange}
onBlur={handleBlur}
maxLength={14}
/>
<p id="cpf-feedback" role="alert">
{mensagem}
</p>
Debounce para consulta automática
Se você preferir que a consulta aconteça automaticamente ao completar 11 dígitos -- sem esperar o blur -- utilize um debounce para evitar chamadas múltiplas.
import { useEffect, useRef } from 'react';
function useDebounce(callback, delay) {
const timerRef = useRef(null);
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);
return (...args) => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => callback(...args), delay);
};
}
// Uso no componente:
const consultarDebounced = useDebounce(async (digits) => {
if (digits.length === 11 && validarCPF(digits)) {
// ... chamar API
}
}, 500);
Com essa abordagem, a consulta é disparada 500ms após o usuário terminar de digitar, oferecendo uma experiência fluida e responsiva.
Testando o componente
Para garantir que o componente funciona corretamente, escreva testes unitários com React Testing Library.
import { render, screen, fireEvent } from '@testing-library/react';
import CPFInput from './CPFInput';
describe('CPFInput', () => {
it('aplica mascara ao digitar', () => {
render(<CPFInput />);
const input = screen.getByPlaceholderText('000.000.000-00');
fireEvent.change(input, { target: { value: '12345678901' } });
expect(input.value).toBe('123.456.789-01');
});
it('limita a 14 caracteres formatados', () => {
render(<CPFInput />);
const input = screen.getByPlaceholderText('000.000.000-00');
fireEvent.change(input, { target: { value: '123456789012345' } });
expect(input.value).toBe('123.456.789-01');
});
it('exibe erro para CPF invalido no blur', () => {
render(<CPFInput />);
const input = screen.getByPlaceholderText('000.000.000-00');
fireEvent.change(input, { target: { value: '11111111111' } });
fireEvent.blur(input);
expect(screen.getByText(/invalido/i)).toBeInTheDocument();
});
});
Estilização com CSS Modules
Para projetos que usam CSS Modules, separe os estilos em um arquivo dedicado.
/* CPFInput.module.css */
.container {
max-width: 360px;
}
.label {
display: block;
margin-bottom: 6px;
font-weight: 600;
color: #333;
}
.input {
width: 100%;
padding: 12px 16px;
font-size: 1.1rem;
border: 2px solid #ccc;
border-radius: 8px;
outline: none;
transition: border-color 0.2s;
}
.input:focus { border-color: #3498db; }
.inputSuccess { border-color: #2ecc71; }
.inputError { border-color: #e74c3c; }
.inputLoading { border-color: #f39c12; }
.feedback {
margin-top: 8px;
font-size: 0.9rem;
}
.feedbackSuccess { color: #2ecc71; }
.feedbackError { color: #e74c3c; }
Perguntas frequentes
O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.
A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.
Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A ANPD orienta que dados de identificação devem ser tratados com o princípio da necessidade.
Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.
Conclusão
Implementar um input de CPF com máscara automática em React é um exercício que combina UX, acessibilidade e integração com APIs externas. Ao construir o componente sem dependências externas, você mantém controle total sobre o comportamento, reduz o tamanho do bundle e simplifica a manutenção.
A integração com a API da CPFHub.io adiciona a camada de validação cadastral em tempo real: ao completar 11 dígitos válidos, o componente consulta automaticamente os dados do titular e retorna nome, gênero e data de nascimento em ~900ms.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece hoje mesmo.
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.



