use-mask-input
| Install | |
|---|---|
npm install use-mask-input |
|
| Latest Version: | 3.14.2 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 9, 2026 |
| Links: | GitHub · npm |
🥸 use-mask-input
Input masks for React and Vue 3. Works with React Hook Form, TanStack Form, vee-validate, Ant Design, and plain inputs.
Documentation · API Reference · TanStack Form · Sponsor
This README covers the common cases. The full guides, every option and the complete API are on the documentation site.
Install
npm install use-mask-input
Usage
import { useMaskInput } from 'use-mask-input';
function PhoneInput() {
const ref = useMaskInput({ mask: '(99) 99999-9999' });
return <input ref={ref} />;
}
Mask syntax
| Token | Accepts | Example |
|---|---|---|
9 |
A digit | '999.999.999-99' |
a |
A letter | 'aa-9999' |
A |
A letter, uppercased | 'AAA-9999' |
* |
A letter or a digit | '***-****' |
[ ] |
An optional part | '99 [9]9999-9999' |
{n}, {n,m} |
The previous token, n times or n to m times | '9{1,5}' |
| An array | Whichever mask fits the input | ['(99) 9999-9999', '(99) 99999-9999'] |
Each one has a guide on the site: static, optional, dynamic, alternator and preprocessing masks.
Numbers and currency
The numeric aliases (numeric, integer, decimal, currency, brl-currency, percentage) format while the user types. Set the separators and the decimal places in options:
function PriceInput() {
const ref = useMaskInput({
mask: 'decimal',
options: { radixPoint: ',', groupSeparator: '.', digits: 2 },
});
return <input ref={ref} />; // typing 1234567,89 shows 1.234.567,89
}
The unmasked value keeps the radix point you chose ('1234,56'). For a real number, add autoUnmask and unmaskAsNumber. The input below shows R$ 1.234,56 while input.value is the number 1234.56:
const ref = useMaskInput({
mask: 'brl-currency',
options: { autoUnmask: true, unmaskAsNumber: true },
});
To follow a locale, read the separators from Intl.NumberFormat:
const parts = new Intl.NumberFormat('de-DE').formatToParts(1000.1);
const groupSeparator = parts.find((p) => p.type === 'group')?.value; // '.'
const radixPoint = parts.find((p) => p.type === 'decimal')?.value; // ','
function PriceInput() {
const ref = useMaskInput({ mask: 'decimal', options: { groupSeparator, radixPoint } });
return <input ref={ref} />;
}
More numeric examples, including min/max and percentages, are in the alias guide.
With React Hook Form
import { useForm } from 'react-hook-form';
import { useHookFormMask } from 'use-mask-input';
function MyForm() {
const { register, handleSubmit } = useForm();
const registerWithMask = useHookFormMask(register);
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...registerWithMask('phone', '(99) 99999-9999')} />
<input {...registerWithMask('email', 'email')} />
<button type="submit">Submit</button>
</form>
);
}
With TanStack Form
import { useForm } from '@tanstack/react-form';
import { useTanStackFormMask } from 'use-mask-input';
function MyForm() {
const maskField = useTanStackFormMask();
const form = useForm({
defaultValues: {
phone: '',
},
onSubmit: async ({ value }) => {
console.log(value);
},
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
event.stopPropagation();
void form.handleSubmit();
}}
>
<form.Field name="phone">
{(field) => {
const inputProps = maskField(
'(99) 99999-9999',
{
name: field.name,
value: field.state.value,
onBlur: field.handleBlur,
onChange: (event) => field.handleChange(event.target.value),
},
);
return <input {...inputProps} placeholder="(00) 00000-0000" />;
}}
</form.Field>
</form>
);
}
With Ant Design
import { Input } from 'antd';
import { useMaskInputAntd } from 'use-mask-input/antd';
function EmailInput() {
const ref = useMaskInputAntd({ mask: 'email' });
return <Input ref={ref} />;
}
Vue 3
Everything above is React. Vue 3 has its own entry point:
import { vMaskInput, useMaskInput } from 'use-mask-input/vue';
The directive
In <script setup>, importing vMaskInput is enough. Vue resolves a vFoo binding to v-foo, so there is no registration step and no plugin.
<script setup>
import { vMaskInput } from 'use-mask-input/vue';
</script>
<template>
<input v-mask-input="'cpf'" />
<input v-mask-input="'(99) 99999-9999'" />
<input v-mask-input="['999-999', '999-999-999']" />
<input v-mask-input="{ mask: 'currency', options: { prefix: 'R$ ' } }" />
</template>
Outside <script setup>, register it yourself: app.directive('mask-input', vMaskInput).
With v-model
v-model works with no extra code. Add autoUnmask and the bound value is the raw one, while the input keeps showing the mask:
<input v-model="cpf" v-mask-input="{ mask: 'cpf', options: { autoUnmask: true } }" />
<!-- displays 123.456.789-01, and cpf === '12345678901' -->
Without autoUnmask, v-model receives the masked string. Directive order does not matter: v-model before or after v-mask-input behaves identically.
Binding null turns masking off and removes any mask already applied, so v-mask-input="enabled ? 'cpf' : null" works as a toggle.
With vee-validate
No helper, no wrapper. useField plus v-model plus the directive is the whole integration, and handleSubmit receives the unmasked value:
<script setup>
import { useField, useForm } from 'vee-validate';
import { vMaskInput } from 'use-mask-input/vue';
const { handleSubmit } = useForm();
const { value, errorMessage } = useField('cpf', (v) => v?.length === 11 || 'Invalid CPF');
const onSubmit = handleSubmit((values) => console.log(values)); // { cpf: '12345678901' }
</script>
<template>
<form @submit="onSubmit">
<input v-model="value" v-mask-input="{ mask: 'cpf', options: { autoUnmask: true } }" />
<span>{{ errorMessage }}</span>
</form>
</template>
With component libraries
Put the directive on the component. Vue applies it to the root element and the mask finds the inner input, so PrimeVue, Element Plus and Ant Design Vue work as-is:
<InputText v-mask-input="'cpf'" />
The composable
For imperative reads, or when you want the raw value without autoUnmask:
<script setup>
import { useMaskInput } from 'use-mask-input/vue';
const { maskRef, unmaskedValue } = useMaskInput('cpf');
const submit = () => console.log(unmaskedValue());
</script>
<template>
<input :ref="maskRef" />
</template>
Vue caveats
unmaskedValue()is not reactive.{{ unmaskedValue() }}renders once and never updates, because reading the DOM registers no reactive dependency. Use it in event handlers and imperative code. For a value the template tracks, usev-modelwithautoUnmask: true.noValuePatching: trueis unsupported. It disables the property accessor that the wholev-modelintegration relies on.- Replace the options object, don't mutate it. Options are compared shallowly, so an in-place mutation may not re-apply the mask.
There is deliberately no <MaskInput> component and no vee-validate helper, because v-model plus the directive already covers both.
APIs
| API | Description |
|---|---|
useMaskInput |
Hook. Returns a ref callback. Default choice. |
useHookFormMask |
Hook. Wraps React Hook Form's register. |
useTanStackFormMask |
Hook. Adds mask to TanStack Form field input props. |
withMask |
Function. Ref callback. Requires React.memo. |
withHookFormMask |
Function. Mask for registered fields. Requires React.memo. |
withTanStackFormMask |
Function. Mask for TanStack input props. Requires React.memo. |
useMaskInputAntd |
Hook. useMaskInput for Ant Design. |
useHookFormMaskAntd |
Hook. useHookFormMask for Ant Design. |
formatWithMask |
Function. Formats a raw value using a mask, without a mounted element. |
unformatWithMask |
Function. Removes the mask from a formatted value, without a mounted element. |
isValidWithMask |
Function. Whether a value, masked or raw, is a complete valid entry for a mask. For schema validators. |
getUnmaskedValue |
Function. Reads the unmasked value off an element, e.g. event.target in onChange. |
isMaskComplete |
Function. Whether the mask on an element is fully filled. |
vMaskInput |
Vue directive. use-mask-input/vue. The Vue default choice. |
useMaskInput (Vue) |
Composable. use-mask-input/vue. Returns { maskRef, unmaskedValue, isComplete }. |
Every hook and with* helper also carries unmaskedValue() and isComplete() on what it returns:
const cpf = useMaskInput({ mask: 'cpf' });
<input ref={cpf} onChange={(e) => console.log(getUnmaskedValue(e.target), cpf.isComplete())} />
The formatters need no mounted element, so they also work in table cells and schema validators:
import { formatWithMask, unformatWithMask, isValidWithMask } from 'use-mask-input';
formatWithMask('12345678901', 'cpf'); // '123.456.789-01'
unformatWithMask('123.456.789-01', 'cpf'); // '12345678901'
isValidWithMask('123.456', 'cpf'); // false
formatWithMask('1234,5', 'brl-currency'); // 'R$ 1.234,50'
Signatures and every option are in the API Reference.
Built-in Aliases
cpf · cnpj · cep · phone-br · date-br · plate-br · br-bank-account · br-bank-agency · currency · brl-currency · credit-card · time · datetime · email · numeric · decimal · integer · percentage · url · ip · mac · ssn
Examples and options for each alias are in the alias guide.
Works With
- TanStack Form (
useTanStackFormMask,withTanStackFormMask). See the TanStack Form guide. - React Hook Form
- Ant Design (
use-mask-input/antd) - React Final Form
- Next.js / SSR
- Vue 3 (
use-mask-input/vue) - vee-validate (no adapter needed)
- Vue component libraries: PrimeVue, Element Plus, Ant Design Vue
License
MIT
Related Packages
An awesome mask directive for Vue.js using vanilla-masker from `https://github.c...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 3.14.2 | >=3.4 | MIT | |
| 3.14.1 | >=3.4 | MIT | |
| 3.14.0 | >=3.4 | MIT | |
| 3.13.0 | >=3.4 | MIT | |
| 3.12.0 | >=3.4 | MIT | |
| 3.11.1 | MIT | ||
| 3.11.0 | MIT | ||
| 3.10.3 | MIT | ||
| 3.10.2 | MIT | ||
| 3.10.1 | |||
| 3.10.0 | |||
| 3.9.0 | |||
| 3.8.0 | |||
| 3.7.4 | |||
| 3.7.3 | |||
| 3.7.2 | |||
| 3.7.1 | |||
| 3.7.0 | |||
| 3.6.1 | |||
| 3.6.0 | |||
| 3.5.2 | |||
| 3.5.1 | |||
| 3.5.0 | |||
| 3.4.2 | |||
| 3.4.1 | |||
| 3.4.0 | |||
| 3.3.8 | |||
| 3.3.7 | |||
| 3.3.6 | |||
| 3.3.5 |
Showing the latest 30 of 52. See every release on npm
Pre-releases (2)
| Version | Released | Vue | License |
|---|---|---|---|
| 4.0.0-alpha.1 | ^3.0.0 | MIT | |
| 3.3.2-beta.1 |