Skip to content
Devix Open Source
UI component v1.0.0 MIT Alpha

Masked Input

Input masks for CNIC, Emirates ID, IBAN, cards and custom patterns.

npm install @devix-labs/masked-input
Vanilla JS React Vue Svelte jQuery
Type in the middle Open

735,500 downloads a week are being served by code nobody maintains: react-input-mask takes 502k/wk with an archived repository, and cleave.js has 17,857 stars and 233k/wk while its most-reacted open issue is its own creator announcing it is deprecated. That leaves imask — good, maintained, and 15.8 kB measured from its published bundle. The bug they all share is the caret: type into the middle of a half-filled field and the cursor leaps to the end, because they keep the formatted string as state and then have to work out where the caret went. Here the state is the raw string you typed, the text is derived from it, and the caret is stored as 'after the nth raw character' — so re-rendering cannot move it. The raw value posts itself through a hidden field, so a plain form submit sends clean data with no JavaScript of yours; mobile keyboards are left alone while composing; and CNIC, Emirates ID, UAE TRN, Saudi VAT, IBAN and the rest ship as named presets taken from the same lengths Devix Validators checks. 2.3 kB, zero dependencies.

What you get

The caret cannot jump

It is pinned to a character you typed, not to an offset in a string that just changed length. Type into the middle, backspace across a separator, paste over a selection — it stays where you are working. There is no delta arithmetic, which is why there is nothing to get wrong.

2.3 kB against a measured 15.8

imask's own published bundle, gzipped on the same machine on the same day. Seven times smaller, no dependencies, and tree-shakable — the DOM-free core on its own is 1.2 kB.

The clean value posts itself

The field's name moves to a hidden input carrying the unmasked characters, so an ordinary form submit sends 4210112345671 rather than 42101-1234567-1 — with no JavaScript of yours involved. react-input-mask #157 has 36 reactions from people asking how to do this.

Mobile keyboards are left alone

Android and iOS compose text before they commit it, and rewriting the field in the middle of that corrupts the keyboard's own buffer — characters double or vanish. Nothing is touched until composition ends, and the test drives real composition events rather than assuming.

Paste whatever you copied

42101-1234567-1, 4210112345671 and 42101 1234567 1 are the same CNIC out of three different places, and all three land correctly, because typing and pasting go through exactly one routine.

The patterns nobody ships

CNIC, NTN, Emirates ID, UAE TRN, Saudi ID and VAT, Qatar ID, Bahrain CPR, PAN, GSTIN, IBAN and cards, by name. Their lengths come from the same table Devix Validators checks, so the mask and the rule cannot disagree.

Your own character classes

Tokens are a map of regular expressions, not three fixed letters. HH:HH:HH:HH:HH:HH with { H: /[0-9A-Fa-f]/ } is a MAC address. cleave.js #529 asks for a class it does not have.

It runs on a server too

The core touches no DOM, so apply() is also how you format a stored IBAN into a table or an invoice. Nothing reads window at import time.

Masked Input — overview

Why it exists

735,500 downloads a week are being served by code nobody maintains.

react-input-mask takes 502 k a week with a read-only repository — its most wanted issue, #239 (+58), is React StrictMode support that will now never come. cleave.js has 17,857 stars and 233 k a week, and its most-reacted open issue, #723 (+34), is the creator announcing it is deprecated.

That leaves imask — good, maintained, and 15.8 kB gzipped, measured from its own published bundle. Two and a half times the weight of the thing it replaced.

The caret

Every one of them has the same bug, and it is the one people actually complain about: type into the middle of a half-filled field and the cursor leaps to the end. imask #916 (+8) is exactly this.

It happens because they keep the formatted string as their state, and then have to work out where the caret went after re-rendering it — arithmetic over a string whose length just changed.

Here the state is the raw string: only the characters you supplied. The field's text is derived from it, and the caret is stored as "after the nth raw character". Re-rendering therefore cannot move it, because the caret is pinned to a character you typed rather than to an offset in something that moved. There is no delta to get wrong.

Tests type into the middle, backspace across a separator, and paste over a selection, and assert the caret's exact position each time.

The other four things they get asked for

  • The value without the mask. react-input-mask #157 (+36) is someone asking how to get it — 36 reactions on a question, which means the answer was not in the docs. Here the widget moves your field's name onto a hidden input holding the raw characters, so a plain HTML form post sends clean data with no JavaScript of yours involved at all.
  • Pasting something already formatted. imask #1063 (+9). 42101-1234567-1, 4210112345671 and 42101 1234567 1 are the same CNIC copied out of three different places, and all three land correctly, because typing and pasting go through one routine.
  • Mobile keyboards. imask #1055 (+8). Android composes text: it fires compositionstart, a run of updates, then compositionend. Rewriting the value in the middle of that corrupts the keyboard's own buffer and characters double or vanish. Nothing here is touched until composition ends, and a test drives real composition events rather than assuming.
  • Your own character classes. cleave #529 (+12) asks for one it does not have. Tokens are a map of regular expressions, so HH:HH:HH:HH:HH:HH with { H: /[0-9A-Fa-f]/ } is a MAC address and AAAAA0000A is an Indian PAN.

The presets nobody ships

CNIC, NTN, Emirates ID, UAE TRN, Saudi ID and VAT, Qatar ID, Bahrain CPR, PAN, GSTIN, IBAN, cards, expiry, dates and times — by name, with the grouping people expect to see.

Their lengths come from the same table Devix Validators checks, so the mask and the validation rule agree by construction rather than because two people remembered the same number.

What we do not claim

That imask is bad. It is the best of them, it is maintained, and if 15.8 kB is not a problem for you it is a perfectly good choice. The claim here is the size, the caret architecture and the regional patterns — not that the incumbent is broken.

Not in 1.0

Money. Formatting an amount as you type is a different problem wearing the same clothes: the grouping separators move as the number grows, the decimal mark depends on the locale, and digits fill from the right rather than the left. Bolting it onto a pattern mask is how the others ended up with those issues open — react-input-mask #192 (+38) and #120 (+25) are both currency, and cleave #405 (+13) is the .-versus-, ambiguity.

It ships as Devix Amount Input, over this same core.

How it compares

Questions

Why does the caret jump in other mask libraries?

Because they keep the formatted string as their state. After each keystroke the field is re-rendered and the caret has to be put back at an offset in a string whose length changed — separators were added or removed — and that arithmetic is where it goes wrong. Keeping the raw string instead removes the arithmetic entirely: the caret is stored as a count of characters you typed, and those did not move.

Do I have to import the stylesheet?

No. A mask only touches the value, so a field you have already styled needs nothing from us. The stylesheet is a small default look and two state classes — dxm--complete and dxm--empty — for when you want them.

What about amounts and currency?

That is a different problem wearing the same clothes: grouping separators move as the number grows, the decimal mark depends on the locale, and digits fill from the right rather than the left. Bolting it onto a pattern mask is how the others ended up with those issues open. It ships as Devix Amount Input over this same core.

Does it validate?

No — a mask stops the shape from being wrong, which is not the same as the number being real. A CNIC has no checksum, an Emirates ID has a Luhn digit and an IBAN has a mod-97. Devix Validators does that, using the same lengths this masks to, so the two cannot drift apart.

Is imask bad?

No. It is the best of them, it is maintained, and if 15.8 kB is not a problem for you it is a reasonable choice. What is claimed here is the size, the caret architecture and the regional patterns — not that the incumbent is broken. What is genuinely alarming is the 735,500 weekly downloads going to an archived repository and a project its own author has deprecated.