# Como implementar autoComplete de CPF para usuários recorrentes

> Aprenda a implementar autocomplete de CPF para usuários recorrentes com segurança, localStorage e validação via API CPFHub.

**Publicado:** 21/06/2025
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-implementar-autocomplete-de-cpf-para-usuarios-recorrentes

---


Para implementar autocomplete de CPF para usuários recorrentes, armazene no localStorage apenas a versão mascarada do CPF (ex: `123.***.***-09`) e um hash de identificação — nunca o número completo. Ao retornar ao site, o usuário vê a sugestão, confirma a identidade e redigita o CPF para revalidação via API. Isso reduz o atrito sem abrir mão da segurança exigida pela LGPD.

## Introdução

Usuários recorrentes -- aqueles que voltam ao seu site ou aplicação com frequência -- não deveriam precisar redigitar o CPF a cada nova interação. Um sistema de autocomplete de CPF bem implementado reduz o atrito no preenchimento de formulários, melhora a experiência do usuário e aumenta a velocidade de checkout ou cadastro.

Contudo, o CPF é um dado sensível, e qualquer implementação de autocomplete precisa equilibrar conveniência e segurança.

---

## Autocomplete nativo do navegador

A forma mais simples de autocomplete é utilizar os atributos HTML que permitem ao navegador salvar e sugerir dados:

```html
<div class="form-group">
 <label for="cpf">CPF</label>
 <input
 type="text"
 id="cpf"
 name="cpf"
 autocomplete="off"
 inputmode="numeric"
 maxlength="14"
 placeholder="000.000.000-00"
 />
</div>
```

### Limitações do autocomplete nativo

Para o CPF, o autocomplete nativo do navegador apresenta limitações:

- **Sem padrão específico**: não existe um valor `autocomplete` oficial para CPF.
- **Formatação inconsistente**: o navegador pode salvar com ou sem máscara.
- **Sem validação**: o dado sugerido não é revalidado automaticamente.

Por isso, uma solução customizada é geralmente mais adequada.

---

## Estratégia 1 -- Autocomplete com localStorage seguro

Armazene uma versão mascarada do CPF no localStorage para exibição, junto com um hash para identificação:

```javascript
// Módulo de armazenamento seguro de CPF
var CpfStorage = (function () {
 var STORAGE_KEY = "cpfhub_saved_cpfs";
 var MAX_ENTRIES = 5;

 function hashCpf(cpf) {
 // Hash simples para identificação (não use para segurança real)
 var hash = 0;
 for (var i = 0; i < cpf.length; i++) {
 var char = cpf.charCodeAt(i);
 hash = (hash << 5) - hash + char;
 hash = hash & hash;
 }
 return hash.toString(36);
 }

 function maskCpf(cpf) {
 // 123.456.789-09 -> 123.***.***-09
 var clean = cpf.replace(/\D/g, "");
 return clean.slice(0, 3) + ".***.***-" + clean.slice(9, 11);
 }

 function save(cpf, name) {
 var clean = cpf.replace(/\D/g, "");
 var entries = getAll();

 var existing = entries.findIndex(function (e) {
 return e.hash === hashCpf(clean);
 });

 if (existing >= 0) {
 entries[existing].lastUsed = Date.now();
 } else {
 entries.push({
 masked: maskCpf(clean),
 hash: hashCpf(clean),
 name: name,
 lastUsed: Date.now(),
 });
 }

 // Manter apenas as entradas mais recentes
 entries.sort(function (a, b) {
 return b.lastUsed - a.lastUsed;
 });
 entries = entries.slice(0, MAX_ENTRIES);

 try {
 localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
 } catch (e) {
 // Silenciar erros de quota ou modo privado
 }
 }

 function getAll() {
 try {
 var raw = localStorage.getItem(STORAGE_KEY);
 return raw ? JSON.parse(raw) : [];
 } catch (e) {
 return [];
 }
 }

 function clear() {
 localStorage.removeItem(STORAGE_KEY);
 }

 return { save: save, getAll: getAll, clear: clear, maskCpf: maskCpf };
})();
```

---

## Componente de autocomplete dropdown

```html
<div class="cpf-autocomplete" id="cpf-autocomplete">
 <div class="cpf-autocomplete__input-wrapper">
 <input
 type="text"
 id="cpf"
 class="cpf-autocomplete__input"
 inputmode="numeric"
 maxlength="14"
 placeholder="000.000.000-00"
 autocomplete="off"
 />
 <div class="cpf-autocomplete__icon" id="cpf-icon"></div>
 </div>
 <div class="cpf-autocomplete__dropdown" id="cpf-dropdown"></div>
 <div class="cpf-autocomplete__feedback" id="cpf-feedback" role="status" aria-live="polite"></div>
</div>
```

```css
.cpf-autocomplete {
 position: relative;
 max-width: 400px;
}

.cpf-autocomplete__input-wrapper {
 position: relative;
}

.cpf-autocomplete__input {
 width: 100%;
 padding: 12px 44px 12px 16px;
 font-size: 16px;
 border: 2px solid #d1d5db;
 border-radius: 8px;
 outline: none;
 transition: border-color 0.2s ease;
}

.cpf-autocomplete__input:focus {
 border-color: #3b82f6;
 box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}

.cpf-autocomplete__dropdown {
 position: absolute;
 top: 100%;
 left: 0;
 right: 0;
 background: #fff;
 border: 1px solid #e2e8f0;
 border-radius: 0 0 8px 8px;
 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
 display: none;
 z-index: 100;
 max-height: 200px;
 overflow-y: auto;
}

.cpf-autocomplete__dropdown.visible {
 display: block;
}

.cpf-autocomplete__item {
 padding: 10px 16px;
 cursor: pointer;
 display: flex;
 justify-content: space-between;
 align-items: center;
 transition: background-color 0.15s ease;
}

.cpf-autocomplete__item:hover {
 background: #f1f5f9;
}

.cpf-autocomplete__item-cpf {
 font-family: monospace;
 font-size: 14px;
 color: #1e293b;
}

.cpf-autocomplete__item-name {
 font-size: 13px;
 color: #64748b;
}

.cpf-autocomplete__feedback {
 font-size: 13px;
 margin-top: 6px;
 min-height: 20px;
}
```

---

## JavaScript do autocomplete

```javascript
var cpfInput = document.getElementById("cpf");
var dropdown = document.getElementById("cpf-dropdown");
var feedback = document.getElementById("cpf-feedback");
var debounceTimer = null;

// Mostrar sugestões ao focar
cpfInput.addEventListener("focus", function () {
 var entries = CpfStorage.getAll();
 if (entries.length > 0 && this.value === "") {
 showDropdown(entries);
 }
});

// Esconder dropdown ao clicar fora
document.addEventListener("click", function (e) {
 if (!e.target.closest("#cpf-autocomplete")) {
 dropdown.classList.remove("visible");
 }
});

function showDropdown(entries) {
 dropdown.innerHTML = entries
 .map(function (entry, index) {
 return (
 '<div class="cpf-autocomplete__item" data-index="' + index + '">' +
 '<span class="cpf-autocomplete__item-cpf">' + entry.masked + "</span>" +
 '<span class="cpf-autocomplete__item-name">' + entry.name + "</span>" +
 "</div>"
 );
 })
 .join("");

 dropdown.classList.add("visible");

 // Eventos de clique nas sugestões
 dropdown.querySelectorAll(".cpf-autocomplete__item").forEach(function (item) {
 item.addEventListener("click", function () {
 // O usuário precisa redigitar -- mostramos apenas para identificação
 feedback.textContent =
 "Por segurança, por favor redigite o CPF completo.";
 feedback.style.color = "#6b7280";
 cpfInput.focus();
 dropdown.classList.remove("visible");
 });
 });
}

// Formatação e validação ao digitar
cpfInput.addEventListener("input", function () {
 dropdown.classList.remove("visible");

 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;

 if (debounceTimer) clearTimeout(debounceTimer);
 var digits = v.replace(/\D/g, "");
 if (digits.length === 11) {
 debounceTimer = setTimeout(function () {
 validateAndSave(digits);
 }, 500);
 }
});

async function validateAndSave(cpf) {
 feedback.textContent = "Validando...";
 feedback.style.color = "#6b7280";

 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) {
 feedback.textContent = "CPF válido - " + data.data.name;
 feedback.style.color = "#059669";

 // Salvar para autocomplete futuro
 CpfStorage.save(cpf, data.data.name);
 } else {
 feedback.textContent = "CPF não encontrado.";
 feedback.style.color = "#dc2626";
 }
 } catch (err) {
 clearTimeout(timeoutId);
 feedback.textContent = "Erro na validação.";
 feedback.style.color = "#dc2626";
 }
}
```

---

## Estratégia 2 -- Autocomplete via conta do usuário

Para plataformas com sistema de login, o autocomplete mais seguro é buscar o CPF da conta do usuário no backend:

```javascript
// Ao carregar a página, verificar se o usuário está logado
async function prefillCpf() {
 try {
 var controller = new AbortController();
 var timeoutId = setTimeout(function () {
 controller.abort();
 }, 5000);

 var res = await fetch("/api/user/profile", {
 credentials: "include",
 signal: controller.signal,
 });
 clearTimeout(timeoutId);
 var user = await res.json();

 if (user && user.cpf) {
 var cpfField = document.getElementById("cpf");
 var masked = CpfStorage.maskCpf(user.cpf);
 cpfField.value = masked;
 cpfField.dataset.prefilled = "true";

 var feedback = document.getElementById("cpf-feedback");
 feedback.textContent = "CPF da sua conta. Clique para editar.";
 feedback.style.color = "#059669";
 }
 } catch (err) {
 // Silenciar -- preenchimento é opcional
 }
}

document.addEventListener("DOMContentLoaded", prefillCpf);
```

---

## Segurança do armazenamento local

### O que armazenar

- **Nunca** armazene o CPF completo no localStorage ou cookies.
- Armazene apenas a versão mascarada (ex: `123.***.***-09`) e um hash.
- Armazene o nome associado para facilitar a identificação.

### Limpeza de dados

Ofereça ao usuário a opção de limpar dados salvos:

```html
<button type="button" class="btn-clear-data" onclick="CpfStorage.clear()">
 Limpar CPFs salvos
</button>
```

### Consentimento

Antes de salvar no localStorage, peça consentimento:

```javascript
function saveCpfWithConsent(cpf, name) {
 var consentKey = "cpfhub_storage_consent";
 if (localStorage.getItem(consentKey) === "true") {
 CpfStorage.save(cpf, name);
 return;
 }

 if (
 confirm(
 "Deseja salvar este CPF para preenchimento automático em futuras visitas?"
 )
 ) {
 localStorage.setItem(consentKey, "true");
 CpfStorage.save(cpf, name);
 }
}
```

---

## Navegação por teclado no dropdown

Para acessibilidade, o dropdown deve ser navegável por teclado:

```javascript
cpfInput.addEventListener("keydown", function (e) {
 var items = dropdown.querySelectorAll(".cpf-autocomplete__item");
 if (!items.length || !dropdown.classList.contains("visible")) return;

 var active = dropdown.querySelector(".cpf-autocomplete__item--active");
 var index = active ? Array.from(items).indexOf(active) : -1;

 if (e.key === "ArrowDown") {
 e.preventDefault();
 if (active) active.classList.remove("cpf-autocomplete__item--active");
 index = (index + 1) % items.length;
 items[index].classList.add("cpf-autocomplete__item--active");
 } else if (e.key === "ArrowUp") {
 e.preventDefault();
 if (active) active.classList.remove("cpf-autocomplete__item--active");
 index = (index - 1 + items.length) % items.length;
 items[index].classList.add("cpf-autocomplete__item--active");
 } else if (e.key === "Enter" && active) {
 e.preventDefault();
 active.click();
 } else if (e.key === "Escape") {
 dropdown.classList.remove("visible");
 }
});
```

---

## Perguntas frequentes

### É seguro salvar CPF no localStorage para autocomplete?
Desde que você armazene apenas a versão mascarada (ex: `123.***.***-09`) e um hash — nunca o CPF completo — o risco é baixo. O localStorage é acessível apenas pelo mesmo domínio, mas não é criptografado, então evite dados completos. A [ANPD](https://www.gov.br/anpd) orienta que dados pessoais devem ser tratados com o princípio da minimização: colete e armazene apenas o estritamente necessário.

### Por que o usuário precisa redigitar o CPF mesmo com autocomplete?
A sugestão serve para identificar qual CPF usar, não para preencher automaticamente. Exigir a redigitação garante que o titular está presente e consciente da ação — o que é especialmente relevante em dispositivos compartilhados e em fluxos regulados pela LGPD.

### Como implementar autocomplete de CPF em SPAs com React ou Vue?
Encapsule o `CpfStorage` em um hook customizado (`useCpfAutocomplete`) ou composable Vue. Use `useEffect`/`onMounted` para carregar as sugestões do localStorage ao montar o componente e chame a API CPFHub.io com debounce de 500ms após o usuário digitar os 11 dígitos.

### O que fazer se o usuário estiver em modo de navegação privada?
O localStorage não persiste em modo privado. Trate o erro silenciosamente no bloco `try/catch` do `CpfStorage.save()` — o formulário continua funcionando normalmente, só sem a sugestão de autocomplete. Para usuários logados, a estratégia via backend (Estratégia 2) funciona independentemente do modo de navegação.

### 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

O autocomplete de CPF para usuários recorrentes melhora a experiência em formulários de uso frequente. A chave é equilibrar conveniência e segurança -- nunca armazenando o CPF completo, sempre pedindo consentimento e revalidando o dado quando necessário. A CPFHub.io oferece validação em ~900ms, uptime de 99,9% e conformidade total com a LGPD, garantindo que cada consulta seja segura e confiável.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito.

