# Como implementar input de CPF com máscara automática em React

> Aprenda a criar um input de CPF com máscara automática em React, com validação em tempo real e integração com a API da CPFHub.io.

**Publicado:** 19/09/2025
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-implementar-input-de-cpf-com-mascara-automatica-em-react

---


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.

```javascript
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](https://www.gov.br/receitafederal/) define o algoritmo oficial de validação dos dígitos verificadores do CPF.

```javascript
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.

```jsx
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-label` ou `label` associado ao input via `htmlFor`.
- `aria-invalid="true"` quando o CPF for inválido.
- `aria-describedby` apontando para a mensagem de erro.

```jsx
<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.

```javascript
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.

```jsx
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.

```css
/* 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](https://www.gov.br/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.

### Leia também

- [Como pedir CPF no checkout sem espantar o cliente](https://cpfhub.io/blog/como-pedir-cpf-no-checkout-sem-espantar-o-cliente)
- [Diferença entre validação de CPF e consulta de CPF: quando usar cada uma](https://cpfhub.io/blog/diferenca-entre-validacao-de-cpf-e-consulta-de-cpf-quando-usar-cada-uma)
- [Como evitar chargebacks usando validação de CPF no checkout](https://cpfhub.io/blog/como-evitar-chargebacks-usando-validacao-de-cpf-no-checkout)
- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)

---

## 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](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece hoje mesmo.

