Skip to content
Devix Open Source

Reference

Options and methods

createMaskedInput(element, {
  mask: 'cnic',
  value: undefined,
  postRaw: true,
  rawName: undefined,
  placeholder: undefined,
  classNames: {},
  onChange: (state, instance) => {},
  onComplete: (state, instance) => {},
});
Option Default
mask — A preset name, a pattern string, or the object below.
value the field's own The raw value to start from. Anything the pattern cannot take is dropped.
postRaw true Move the field's name to a hidden input carrying the unmasked value.
rawName the field's name Post the raw value under a different name, keeping the visible one too.
placeholder — Sets the field's placeholder.
classNames input, complete, empty — your classes alongside ours.
onChange Every time the value changes, with { masked, raw, complete, empty } and the widget.
onComplete The moment every slot is filled.

Neither fires on mount. A value you passed in is not a change, and a controlled React field whose onChange fires during its own construction is how you get a render loop. The field is masked and painted before either callback can run.

The mask object

Default
pattern — Required.
tokens Your own classes: { H: /[0-9A-Fa-f]/ }.
transform 'upper' or 'lower', applied to everything typed.
lazy true Separators appear as they are reached rather than up front.
placeholderChar '' What an unfilled slot shows when lazy is off.

Methods

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

cnic.getValue();          // '42101-1234567-1'
cnic.getRaw();            // '4210112345671'
cnic.read();              // { masked, raw, complete, empty }
cnic.setValue('4210112345671');
cnic.setMask('card-amex');   // keeps what is typed, regroups it
cnic.destroy();

setMask does not empty the field. Switching a card field from 16 digits to Amex's 15 keeps the digits and regroups them, which is what happens when you detect the issuer from the first few.

destroy() puts the field back exactly as it was — the name returns, the hidden input goes, the classes come off — so a second widget on the same field still posts.

The core, without a field

Everything is exported and none of it touches the DOM, so it runs on a server too:

import { createMask, PRESETS } from '@devix-labs/masked-input/core';

const mask = createMask('cnic');

mask.apply('4210112345671');
// { masked: '42101-1234567-1', raw: '4210112345671', complete: true }

mask.accept('42101-1234-abc');   // '421011234' — what the pattern will take
mask.capacity;                   // 13
mask.caretAfter('42101', 5);     // 6 — past the separator
mask.rawBefore('42101-1', 6);    // 5 — the separator is not a character you typed

apply() is also the right way to format a value you already have, for a table or a PDF:

createMask('iban').apply(account.iban).masked;   // 'AE07 0331 2345 6789 0123 456'

Validating

A mask stops the shape from being wrong. It does not know whether the number is real — a CNIC has no checksum, an Emirates ID has a Luhn digit, an IBAN has a mod-97. That is Devix Validators, which uses the same lengths:

import { validate } from '@devix-labs/validators';

const { raw } = cnic.read();
validate('emirates-id', raw);   // { valid, reason, details }
Updated 15 Sep 2026