Skip to content
Devix Open Source

Guide

React, Vue, Svelte and a plain page

React

import { MaskedInput } from '@devix-labs/masked-input/react';

<MaskedInput
  mask="cnic"
  name="cnic"
  value={cnic}
  onChange={(raw) => setCnic(raw)}
  placeholder="42101-1234567-1"
/>

value and onChange are the raw value, which is what you are storing.

The widget owns the field's text and React never writes value to the DOM, so a re-render in the middle of fast typing cannot move the caret — which is what react-input-mask's StrictMode issue (#239, +58) is ultimately about. Changing mask regroups what is typed rather than rebuilding the field.

react-hook-form

<Controller
  name="cnic"
  control={control}
  rules={{ validate: (v) => v?.length === 13 || 'Thirteen digits' }}
  render={({ field }) => (
    <MaskedInput mask="cnic" value={field.value} onChange={field.onChange} onBlur={field.onBlur} />
  )}
/>

Vue

<script setup>
import { ref } from 'vue';
import { MaskedInput } from '@devix-labs/masked-input/vue';

const cnic = ref('');
</script>

<template>
  <MaskedInput v-model="cnic" mask="cnic" name="cnic" />
</template>

v-model is the raw value.

Svelte

<script>
  import { maskedInput } from '@devix-labs/masked-input/svelte';

  let cnic = '';
</script>

<input use:maskedInput={{ mask: 'cnic', onChange: (s) => (cnic = s.raw) }} name="cnic" />

A web component

<script type="module" src="https://devix.pk/cdn/oss/masked-input@1.0.0/element.js"></script>

<dx-masked-input mask="emirates-id" name="eid" placeholder="784-1990-1234567-1"></dx-masked-input>

It creates the <input> itself, or wraps one you put inside — which is the way to keep a field's own label, attributes and place in the tab order:

<label for="iban">IBAN</label>
<dx-masked-input mask="iban">
  <input id="iban" name="iban" inputmode="text">
</dx-masked-input>

It fires change and complete, and exposes .value, .raw and .instance.

Laravel and Blade

The hidden-field behaviour means the server side is ordinary. Nothing in your controller knows a mask was involved:

<form method="post">
  @csrf
  <input name="cnic" id="cnic" value="{{ old('cnic') }}">
</form>

<script type="module">
  import { createMaskedInput } from '/cdn/oss/masked-input@1.0.0/index.js';

  createMaskedInput(document.querySelector('#cnic'), { mask: 'cnic' });
</script>
$request->validate(['cnic' => ['required', 'digits:13', new Cnic]]);

old('cnic') is the raw value, and the widget re-masks it on load, so a failed validation redisplays correctly — which is the thing that usually breaks.

Devix Laravel Validators ships the Cnic, EmiratesId, Iban and the rest of those rules, checking the same lengths this package masks.

Enhancing a field you already have

None of the above is required. The widget takes an existing <input> and leaves everything about it alone except the value:

document.querySelectorAll('[data-mask]').forEach((field) => {
  createMaskedInput(field, { mask: field.dataset.mask });
});
<input name="cnic" data-mask="cnic">
<input name="card" data-mask="card">
<input name="mac"  data-mask="mac">
Updated 15 Sep 2026