Máscaras
Da formato mientras se escribe, comprueba el dígito verificador de documentos y oculta datos sensibles.
No sustituye el campo por otro control: es comportamiento sobre tu <input>, que sigue con su
name y con el estilo que el proyecto ya aplica.
El CPF es el documento fiscal de las personas en Brasil, y el importe está en reales brasileños. Escribe un CPF no válido y sal del campo: el navegador pasa a bloquear el envío.
Ejemplos
Todo es el mismo data-tuc-mask con otro formato. El texto de espera sale del patrón — nadie escribe 000.000.000-00 a mano. Los formatos con nombre siguen documentos y convenciones de Brasil; el patrón libre sirve para cualquier otra cosa.
CNPJ
El documento fiscal de las empresas en Brasil. Acepta el formato nuevo, con letras.
CPF o CNPJ
Cambia de patrón según la longitud.
Teléfono
Fijo brasileño con 8 dígitos o móvil con 9.
Móvil
Siempre con 9 dígitos.
CEP
cep, el código postal brasileño.
Tarjeta
card, en grupos de cuatro.
Fecha y hora, en texto
date y time — solo la máscara, sin calendario.
Moneda sin símbolo
currency, y tres decimales con data-decimals="3".
Otra moneda
data-currency="USD" sobre el currency.
Patrón libre
data-tuc-mask="##/##", caducidad de la tarjeta.
Patrón con letras
AAA-####: A es letra, # es dígito. El patrón libre no pasa a mayúsculas.
Con error del servidor
aria-invalid="true" en el campo.
CNPJ no válido.
Cómo usar
Marca el <input> con el formato, y se inicializa solo al cargar y en cada
htmx:afterSwap. El componente pone la clase .tuc-input en el campo y un inputmode numérico
cuando el patrón no acepta letras, para que el móvil abra el teclado de números.
<input name="cpf" data-tuc-mask="cpf" data-validate="true">
<input name="document" data-tuc-mask="cpf-cnpj">
<input name="phone" data-tuc-mask="phone">
<input name="amount" data-tuc-mask="brl">
<input name="expiry" data-tuc-mask="##/##">| Formato | Resultado |
|---|---|
cpf | 111.444.777-35 |
cnpj | 11.222.333/0001-81 — acepta letras en las 12 primeras posiciones |
cnpj-numeric | Lo mismo, rechazando letras |
cpf-cnpj | Uno u otro, según la longitud |
phone | (69) 3344-5566 o (69) 99988-7766 |
mobile | (69) 99988-7766 |
cep | 69900-000 |
date time | 25/12/2026 · 14:30 |
card | 4111 1111 1111 1111 |
real | R$ 1.234,50 |
currency | 1.234,50, sin símbolo — con data-currency, el de la moneda |
| patrón libre | # dígito, A letra, * ambos; el resto entra tal cual |
| Atributo | Por defecto | Para qué |
|---|---|---|
data-tuc-mask | — | Nombre del formato o patrón libre |
data-validate | false | Comprueba el dígito verificador al salir del campo |
data-error-text | del formato | Mensaje del navegador cuando la validación falla |
data-decimals | 2 | Decimales en la moneda |
data-currency | — | Código de la moneda, como BRL o USD |
data-tuc-reveal | — | Ojo para mostrar y ocultar; el valor elige el modo |
data-reveal-visible | 2 | Caracteres visibles en el modo end |
data-reveal-mode | según el campo | end, email o all |
En el formulario de Django
class CustomerForm(forms.ModelForm):
class Meta:
model = Customer
fields = ["document", "phone", "credit_limit"]
widgets = {
"document": forms.TextInput(attrs={"data-tuc-mask": "cpf-cnpj", "data-validate": "true"}),
"phone": forms.TextInput(attrs={"data-tuc-mask": "phone"}),
"credit_limit": forms.TextInput(attrs={"data-tuc-mask": "brl"}),
}El campo envía el texto tal como está en pantalla: 111.444.777-35, R$ 1.234,50. Límpialo en el clean_ —
la validación del navegador protege la pantalla, no el servidor.
def clean_document(self):
return re.sub(r"[^0-9A-Z]", "", self.cleaned_data["document"].upper())
def clean_credit_limit(self):
text = re.sub(r"[^\d,]", "", self.cleaned_data["credit_limit"])
return Decimal(text.replace(",", ".")) if text else NoneEn JavaScript
const m = new Tucano.Mask('#amount', { format: 'brl' });
m.getRaw(); // '123450' — sin puntuación
m.getNumber(); // 1234.5 — solo en los formatos de moneda; en los demás, null
m.setValue('99'); // da formato y dispara tucano:change
m.isValid(); // comprueba el dígito verificador, en los formatos que lo tienen
m.destroy();
document.querySelector('#amount').addEventListener('tucano:change', (e) => {
e.detail.value; // 'R$ 1.234,50'
e.detail.raw; // '123450'
e.detail.number; // 1234.5
});Las utilidades detrás de la máscara también vienen listas, para validar o dar formato fuera de un campo:
Tucano.mask.validateCPF('111.444.777-35') // true
Tucano.mask.validateCNPJ('11.222.333/0001-81') // true
Tucano.mask.validateCpfCnpj('123.456.789-01') // false
Tucano.mask.format('12345678901', 'cpf') // '123.456.789-01'
Tucano.mask.applyCurrency('12345', { currency: 'BRL' }) // 'R$ 123,45'
Tucano.mask.maskEmail('contato@empresa.com.br') // 'c••••••@empresa.com.br'Validación
Con data-validate, el dígito verificador se comprueba al salir del campo. Si falla, el campo recibe
aria-invalid="true" y setCustomValidity, y el propio navegador bloquea el envío — sin una línea en el proyecto.
Y el acierto se ve al momento: en cuanto el valor está completo y correcto, el campo se pone verde, sin esperar a salir de
él. El error espera — mientras se escribe, un CPF a medias nunca se pone rojo; solo se señala al salir del campo, y vuelve a
desaparecer cuando la persona lo corrige. El verde es el atributo data-tuc-valid, pintado con
--tuc-success.
Valen cpf, cnpj, cnpj-numeric y cpf-cnpj; los demás formatos no tienen dígito
que comprobar. Un campo vacío pasa: la obligatoriedad es cosa de required. Mientras se escribe el error desaparece, y al volver al campo
también — incluida la marca que vino del servidor, porque la persona está corrigiendo.
<input name="cpf" data-tuc-mask="cpf" data-validate="true" data-error-text="Revisa el CPF">CNPJ alfanumérico
El formato nuevo mantiene las 14 posiciones y la misma máscara: las 12 primeras aceptan letras, las 2 últimas siguen siendo numéricas, y el dígito verificador usa el código ASCII menos 48 — lo que deja el cálculo antiguo válido para CNPJ solo de números. cnpj acepta los dos y pasa las letras a mayúsculas; cnpj-numeric rechaza letras, para quien todavía no puede recibirlas. Confirma la fecha de entrada en vigor en la Nota Técnica de la Receita Federal de Brasil antes de exigirlo en producción.
Campo sensible
data-tuc-reveal pone un ojo para mostrar y ocultar. Un campo que ya llega relleno nace oculto,
con solo el final visible; un campo vacío nace visible, porque quien escribe necesita ver. La contraseña es la excepción: nace siempre oculta.
CPF de un registro
Modo end, con dos visibles.
Tarjeta
data-reveal-visible="4" deja los cuatro últimos.
Correo
En type="email", deja el dominio.
Clave de API
data-tuc-reveal="all" no muestra nada.
Contraseña
En type="password" el ojo solo cambia el type.
Vacío
Nace visible; el ojo lo oculta después de escribir.
| Modo | Cuándo | Resultado |
|---|---|---|
end, el predeterminado | Documento, tarjeta, cuenta, teléfono | •••• •••• •••• 1234 |
email | Automático en type="email" | j•••••••••@empresa.com.br |
all | Token, clave de API | •••••••••••••••••••• |
<input name="cpf" value="111.444.777-35" data-tuc-mask="cpf" data-tuc-reveal>
<input name="card" data-tuc-mask="card" data-tuc-reveal data-reveal-visible="4">
<input name="api_key" data-tuc-reveal="all">
<input type="email" name="email" data-tuc-reveal>
<input type="password" name="password" data-tuc-reveal>Lo que el formulario envía no cambia por estar oculto. Fuera de type="password", el name pasa a un
<input type="hidden"> con el valor real, y el campo visible muestra los puntos. Mientras está oculto, el campo es de solo
lectura: escribir sobre los puntos sobrescribiría el valor sin que la persona se diera cuenta. Los separadores siguen en su sitio,
para que la forma siga siendo reconocible: •••.•••.•••-35.
Con máscara, el oculto lleva el valor sin puntuación
En un campo con data-tuc-mask y data-tuc-reveal, lo que llega al servidor es 11144477735, y no 111.444.777-35 — es el getRaw(). Sin máscara, va el texto tal como se escribió.
Por qué nacer oculto, y por qué el correo es al revés
Capturas de pantalla, grabaciones de soporte, alguien mirando por encima del hombro: el dato completo aparecía sin que nadie lo pidiera. Nacer oculto es la diferencia entre exponer por defecto y exponer por elección. En el correo el dominio es lo que ayuda a reconocer la cuenta y la parte local es lo que identifica a la persona — dejar el final revelaría om.br y ocultaría lo útil. Sin @, lo oculta todo.
Texto suelto en la pantalla
No todo dato sensible está en un campo: el CPF en la pantalla de un cliente, la tarjeta en una celda de tabla, la clave
de API en un párrafo. data-tuc-reveal vale en cualquier elemento, en los mismos modos, y junto con
data-tuc-format. El texto nace oculto, con el ojo al lado.
| Cliente | CPF | Tarjeta | Correo |
|---|---|---|---|
| Juan García | 11144477735 | 4111 1111 1111 1234 | juan.garcia@empresa.com |
Clave de API: sk_live_a1b2c3d4e5f6
<td><span data-tuc-format="cpf" data-tuc-reveal>{{ customer.cpf }}</span></td>
<td><span data-tuc-reveal data-reveal-visible="4">{{ card.number }}</span></td>
<p>Clave de API: <span data-tuc-reveal="all">{{ api_key }}</span></p>En la pantalla, ocultar es solo visual
El valor completo sigue en el HTML: quien abre el código de la página lo ve. Sirve contra capturas, grabaciones y quien mira por encima del hombro. Si el dato no puede llegar al navegador, ocúltalo en el servidor y envía solo lo que puede aparecer, como los cuatro últimos dígitos de la tarjeta.
Solo para mostrar
Para lo que ya viene de la base de datos y no se edita, no hace falta un campo: data-tuc-format da formato al texto del
propio elemento.
| Marcado | En pantalla |
|---|---|
data-tuc-format="cpf" · 12345678901 | 12345678901 |
data-tuc-format="cnpj" · 11222333000181 | 11222333000181 |
data-tuc-format="cnpj" · 12ABC34501DE35 | 12ABC34501DE35 |
data-tuc-format="phone" · 69999887766 | 69999887766 |
data-tuc-format="cep" · 69900000 | 69900000 |
data-tuc-format="brl" · 1234.5 | 1234.5 |
data-tuc-format="currency" data-decimals="3" · 12.5 | 12.5 |
<span data-tuc-format="cpf">{{ customer.cpf }}</span>
<span data-tuc-format="brl">{{ order.total }}</span>
<span data-tuc-format="phone" data-value="{{ customer.phone }}"></span>Acepta cpf, cnpj, cpf-cnpj, phone, mobile, cep,
card, real, currency y patrón libre, con data-decimals y data-currency
en la moneda. El valor sale del texto del elemento o de data-value. La moneda lee el punto decimal de la base de datos
(1234.5) y también la coma. Un contenido que no completa el patrón vuelve tal como vino, en lugar de salir a medias
y parecer corrupto. Para HTML insertado por otro camino que no sea HTMX, Tucano.autoFormat(element).
Teclado y accesibilidad
La máscara usa el teclado del navegador, sin atajos propios. De lo que se ocupa es de que el cursor no se pierda.
| Tecla | Acción |
|---|---|
Backspace Delete | Sobre un separador, borra el carácter vecino, en lugar de trabarse |
| Pegar | Descarta lo que no cabe: abc111x444.777--35 se convierte en 111.444.777-35 |
| Escribir en la moneda | Se llena de derecha a izquierda, con el cursor siempre al final |
Tab hasta el ojo | Es un <button>: Enter y Espacio alternan |
El ojo anuncia "Mostrar" u "Ocultar" y el estado con aria-pressed. Al mostrar, el foco va al campo. Si falla la
validación, el campo queda con aria-invalid="true", que el lector de pantalla lee como no válido.
API
Generada a partir del código en cada build — si algo no está aquí, no existe.
[data-tuc-mask], [data-tuc-reveal] [data-tuc-format]:not([data-tuc-formatted])new Tucano.Mask(alvo, opcoes) Tucano.autoFormat()data-currency data-decimals data-error-text data-reveal-mode data-reveal-visible data-tuc-format data-tuc-mask data-tuc-reveal data-validate data-valuegetRaw getNumber setValue isValid destroytucano:changeOpciones
Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.
| Opción | Por defecto | Para qué |
|---|---|---|
format | null | nome de FORMATS ou gabarito livre |
validate | false | valida no blur e bloqueia o submit |
decimals | 2 | |
currency | null | 'BRL' formata com R$ |
reveal | false | olhinho para mostrar e ocultar |
revealVisible | 2 | quantos caracteres ficam a mostra no modo 'end' |
revealMode | null | 'end' | 'email' | 'all'. null decide pelo campo |
locale | undefined | |
errorText | null | |
onChange | null |