Skip to content
Devix Open Source

Guide

Getting started

npm install @devix-labs/masked-input
import { createMaskedInput } from '@devix-labs/masked-input';

createMaskedInput(document.querySelector('#cnic'), { mask: 'cnic' });
<script type="module" src="https://devix.pk/cdn/oss/masked-input@1.0.0/masked-input.min.js"></script>

<dx-masked-input mask="emirates-id" name="eid"></dx-masked-input>

The stylesheet is optional. A mask only touches the value, so a field you have already styled needs nothing from us; import @devix-labs/masked-input/styles.css only if you want the default look.

Why the caret does not jump

This is the bug every mask library has, so it is worth saying how this one avoids it rather than claiming it is careful.

The state kept here is the raw string — only the characters you actually typed, with no separators in it. The text in the field is derived from that. The caret is not stored as an offset into the field; it is stored as "after the nth raw character".

So when the field is re-rendered, the caret is put back after that same character. There is no arithmetic to get wrong, because nothing needs to be adjusted: the thing the caret is pinned to did not move.

Type into the middle of a half-filled CNIC and the caret stays where you are typing. Paste over a selection and it lands at the end of what you pasted.

The value you store is not the value you see

const cnic = createMaskedInput(field, { mask: 'cnic' });

cnic.getValue();   // '42101-1234567-1'  — what the user sees
cnic.getRaw();     // '4210112345671'    — what you store

And if you are not using JavaScript at all, you still get the clean one. The widget moves the field's name onto a hidden input carrying the raw value, so a plain form post sends cnic=4210112345671 with nothing else from you:

<form method="post">
  <input name="cnic" id="cnic">
</form>

Pass postRaw: false to turn that off, or rawName to post both.

Patterns

0 or 9 a digit
a a letter
A a letter, upper-cased
* a letter or a digit
\ escapes the next character
anything else a literal, written for you
createMaskedInput(field, { mask: '0000 0000 0000 0000' });   // a card
createMaskedInput(field, { mask: 'AA00 0000' });             // a sort code
createMaskedInput(field, { mask: '\\#000' });                // a literal hash

Your own character classes

createMaskedInput(field, {
  mask: { pattern: 'HH:HH:HH:HH:HH:HH', tokens: { H: /[0-9A-Fa-f]/ }, transform: 'upper' },
});

Any regular expression, under any letter. Three fixed classes is not a language.

Presets

Named, so you do not have to remember the grouping — and taken from the same numbers Devix Validators checks, so the mask and the rule cannot disagree.

createMaskedInput(field, { mask: 'emirates-id' });

cnic · ntn · emirates-id · uae-trn · saudi-id · saudi-vat · qatar-id · bahrain-cpr · pan · gstin · card · card-amex · expiry · cvc · cvc-4 · iban · date · date-iso · time · time-seconds · mac · hex

Showing the shape before it is filled

By default the field is empty when it is empty, and separators appear as you reach them. If the user cannot be expected to know the shape, show it:

createMaskedInput(field, {
  mask: { pattern: '00/00/0000', lazy: false, placeholderChar: '_' },
});
// __/__/____

Where to go next

Updated 15 Sep 2026