A regra da máscara
| Posições | Aceita | Símbolo típico de máscara |
|---|---|---|
| 1 a 12 | letra ou número | A, C ou S, conforme a biblioteca |
| 13 e 14 | apenas número | 9 ou 0 |
| separadores | . . / - nas mesmas posições de sempre | — |
JavaScript puro (sem biblioteca)
Trinta linhas resolvem o caso completo: normaliza para maiúsculas, filtra caracteres inválidos, bloqueia letra no dígito verificador e reposiciona o cursor.
function mascararCnpj(valor) {
// 1. só letras e números, sempre em maiúsculas
const limpo = String(valor).toUpperCase().replace(/[^0-9A-Z]/g, '').slice(0, 14);
// 2. as duas últimas posições só aceitam número
const base = limpo.slice(0, 12);
const dv = limpo.slice(12).replace(/\D/g, '');
const completo = base + dv;
// 3. monta a pontuação progressivamente
let saida = completo.slice(0, 2);
if (completo.length > 2) saida += '.' + completo.slice(2, 5);
if (completo.length > 5) saida += '.' + completo.slice(5, 8);
if (completo.length > 8) saida += '/' + completo.slice(8, 12);
if (completo.length > 12) saida += '-' + completo.slice(12, 14);
return saida;
}
const campo = document.querySelector('#cnpj');
campo.addEventListener('input', (evento) => {
const noFim = campo.selectionStart === campo.value.length;
campo.value = mascararCnpj(campo.value);
// mantém o cursor no fim quando o usuário está digitando normalmente
if (noFim) campo.setSelectionRange(campo.value.length, campo.value.length);
});
// cobre colagem com formato estranho e preenchimento automático
campo.addEventListener('change', () => {
campo.value = mascararCnpj(campo.value);
});
Nas bibliotecas mais usadas
// IMask — 'a' aceita letra, '0' aceita dígito, '*' aceita ambos
IMask(document.querySelector('#cnpj'), {
mask: '**.***.***/****-00',
prepare: (valor) => valor.toUpperCase(),
definitions: { '*': /[0-9A-Za-z]/ },
});
// Maska (Vue/vanilla) — '#' dígito, '@' letra, '*' alfanumérico
// <input v-maska data-maska="**.***.***/****-##" data-maska-tokens="*:[0-9A-Za-z]|#:[0-9]">
// Cleave.js
new Cleave('#cnpj', {
blocks: [2, 3, 3, 4, 2],
delimiters: ['.', '.', '/', '-'],
uppercase: true,
});
// jQuery Mask Plugin — 'S' letra, '0' dígito, 'A' alfanumérico
$('#cnpj').mask('AA.AAA.AAA/AAAA-00', {
translation: { A: { pattern: /[0-9A-Za-z]/ } },
onKeyPress: (v, e, campo) => campo.val(v.toUpperCase()),
});
// Angular com ngx-mask — 'S' letra, '0' dígito
// <input mask="SS.SSS.SSS/SSSS-00" [validation]="false" oninput="this.value = this.value.toUpperCase()">
// No módulo:
provideNgxMask({ patterns: { S: { pattern: new RegExp('[0-9A-Za-z]') } } });
// React sem biblioteca — controlado, usando a função mascararCnpj acima
export function CampoCnpj() {
const [valor, setValor] = useState('');
return (
<input
value={valor}
inputMode="text"
autoCapitalize="characters"
maxLength={18}
placeholder="00.000.000/0000-00"
onChange={(evento) => setValor(mascararCnpj(evento.target.value))}
/>
);
}
// Antes (só dígitos):
edtCnpj.EditMask := '!99.999.999/9999-99;1;_';
// Depois (letras e números nas 12 primeiras posições):
edtCnpj.EditMask := '!CC.CCC.CCC/CCCC-99;1;_';
// Na TMaskEdit do Delphi:
// 9 = dígito opcional 0 = dígito obrigatório
// C = caractere obrigatório qualquer A = alfanumérico obrigatório
// > força maiúsculas do ponto em diante
edtCnpj.EditMask := '!>CC.CCC.CCC/CCCC-99;1;_';
Detalhes de usabilidade que fazem diferença
- Não use
inputmode="numeric". No celular ele abre o teclado numérico e o usuário não consegue digitar as letras. Useinputmode="text"comautocapitalize="characters". - Aceite colagem em qualquer formato. O usuário vai colar com máscara, sem máscara, com espaços e em minúsculas. Limpe tudo e remonte.
- Não bloqueie a digitação de letras "por segurança". É o erro mais comum agora: um
onkeypressantigo filtrando não-dígitos torna impossível informar um CNPJ válido. - Ajuste o
autocomplete. Useautocomplete="off"ou um nome próprio para o campo, evitando que o navegador sugira valores antigos só numéricos. - Atualize o
placeholdere a mensagem de erro. "Digite apenas números" precisa sair de todas as telas.