Skip to content

Vue ​

The v-mask directive from tinyval-mask/vue puts a mask on a field. The package doesn't need Vue at runtime: the directive is a plain object, Vue is used only for types.

vue
<script setup lang="ts">
import { ref } from 'vue';
import { phone, money } from 'tinyval-mask/presets';
import { vMask } from 'tinyval-mask/vue';

// create masks once, not inline in the template
const phoneMask = phone();
const moneyMask = money({ currency: '₽' });

const tel = ref('');
const amount = ref('');
</script>

<template>
  <input v-model="tel" v-mask="phoneMask" />
  <input v-model="amount" v-mask="moneyMask" inputmode="decimal" />
  <input v-mask="'99.99.9999'" />
</template>
  • v-model gets the masked value: +7 (999) 123-45-67. To get the unmasked value add the .raw modifier — see below.
  • Create masks in <script>: an expression like v-mask="phone()" creates a new mask on every render.
  • The directive can go on a wrapper — it finds the <input> inside. That's how masks work with UI library fields.
  • Global registration: app.directive('mask', vMask).

Unmasked value — .raw ​

With the .raw modifier the field shows the mask while v-model gets the value without it — the same as unmask returns. It can be saved right away:

vue
<input v-model="tel" v-mask.raw="phoneMask" />    <!-- tel: '79991234567' -->
<input v-model="amount" v-mask.raw="moneyMask" /> <!-- amount: '1500.5' -->

A value from the model is shown with the mask: tel = '79991234567' → the field shows +7 (999) 123-45-67. The caret doesn't jump when editing in the middle, even when Vue re-renders the field.

Visible template ​

Pass a placeholder character — on focus the field shows the whole template and the caret goes to the first empty position:

vue
<input v-model="tel" v-mask.raw="{ mask: phoneMask, placeholder: '_' }" placeholder="Phone" />
<!-- on focus: +7 (___) ___-__-__   typing: +7 (999) 12_-__-__ -->
  • The template doesn't go into v-model: without .raw it holds +7 (999) 12, with .raw — 7999912.
  • An empty field is cleared on blur — the regular placeholder hint is shown. A partially filled one keeps the template.
  • Number masks have no template: their length isn't fixed.

With tinyval-vue ​

v-mask works on <TvField> and on any tinyval-vue form field. With .raw the form gets the digits right away, and the schema validates them without unmask:

vue
<script setup lang="ts">
import { object, string, pipe, length } from 'tinyval';
import { useForm, TvForm, TvField, TvError } from 'tinyval-vue';
import { phone } from 'tinyval-mask/presets';
import { vMask } from 'tinyval-mask/vue';

const phoneMask = phone();

const Contact = object({
  phone: pipe(string(), length(11)),
});

const form = useForm(Contact, { initial: { phone: '' } });
</script>

<template>
  <TvForm :form="form" @submit="(data) => api.post('/contacts', data)">
    <TvField name="phone" v-mask.raw="{ mask: phoneMask, placeholder: '_' }" inputmode="tel" />
    <TvError name="phone" />
  </TvForm>
</template>

The field shows +7 (999) 123-45-67; form.values.phone and data have 79991234567.

UI libraries ​

vue
<el-input v-model="tel" v-mask="phoneMask" />
<v-text-field v-model="tel" v-mask="phoneMask" />

See also ​