@desource/phone-mask-nuxt

🎯 Zero-config Nuxt module for international phone masking. Powered by @desource/phone-mask with Google libphonenumber sync.
62,712 31
Install
npm install @desource/phone-mask-nuxt
Latest Version:1.7.0
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: stefanpopov

@desource/phone-mask-nuxt

Nuxt module for phone input with Google's libphonenumber data

npm version code coverage SonarCloud Ask CodeWiki Ask Context7 Ask DeepWiki license

Drop-in Nuxt module with auto-imports, SSR support, and zero configuration.

✨ Features

  • 🎯 Zero Config β€” Works out of the box
  • πŸ”„ Auto-imports β€” Component, directive, and types by default
  • 🌐 SSR Compatible β€” Server-side rendering ready
  • 🎨 Styleable β€” Bring your own styles or use defaults
  • πŸ”§ TypeScript β€” Fully typed
  • ⚑ Optimized β€” Tree-shaking and code splitting

πŸ“¦ Installation

npm install @desource/phone-mask-nuxt
# or
yarn add @desource/phone-mask-nuxt
# or
pnpm add @desource/phone-mask-nuxt

πŸš€ Setup

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt']
});

That's it! The component, directive, and related mask types are now auto-imported. You can additionally enable helper/composable auto-imports via module options.

πŸ“– Usage

Basic Component

<template>
  <div>
    <PhoneInput v-model="phone" country="US" />
    <p>Phone: {{ phone }}</p>
  </div>
</template>

<script setup lang="ts">
const phone = ref('');
</script>

With Auto-detection

<template>
  <PhoneInput v-model="phone" detect @country-change="onCountryChange" />
</template>

<script setup>
const phone = ref('');

const onCountryChange = (country) => {
  console.log('Detected country:', country.name);
};
</script>

Using the Directive

<template>
  <div class="phone-wrapper">
    <select v-model="selectedCountry">
      <option value="US">πŸ‡ΊπŸ‡Έ +1</option>
      <option value="GB">πŸ‡¬πŸ‡§ +44</option>
      <option value="DE">πŸ‡©πŸ‡ͺ +49</option>
    </select>

    <input
      v-phone-mask="{
        country: selectedCountry,
        onChange: handleChange
      }"
      class="phone-input"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { PMaskPhoneNumber } from '#imports';

const selectedCountry = ref('US');
const phone = ref<PMaskPhoneNumber>({
  full: '',
  fullFormatted: '',
  digits: ''
});

const handleChange = (nextPhone: PMaskPhoneNumber) => {
  phone.value = nextPhone;
};
</script>

SSR + Auto-imports in Practice

@desource/phone-mask-nuxt is SSR-safe: the module config is evaluated on server, while the UI component is registered in client mode.

<script setup lang="ts">
const phone = ref('');
const valid = ref(false);
</script>

<template>
  <!-- Auto-imported: no explicit component import required -->
  <PhoneInput id="phone" name="phone" v-model="phone" country="US" @validation-change="valid = $event" />

  <p v-if="valid">βœ“ Ready to submit</p>
</template>

Backend Payload (Raw Digits)

<script setup lang="ts">
import type { PMaskPhoneNumber } from '#imports';

const phoneDigits = ref('');

const handlePhoneChange = async (phone: PMaskPhoneNumber) => {
  await $fetch('/api/profile/phone', {
    method: 'POST',
    body: {
      phoneDigits: phone.digits, // unformatted value for backend
      phoneFull: phone.full // optional full number with country code
    }
  });
};
</script>

<template>
  <PhoneInput v-model="phoneDigits" country="US" @change="handlePhoneChange" />
</template>

Auto-importing Helpers + Composable

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt'],
  phoneMask: {
    // Disable auto-imported styles, component, and directive if you don't need them
    css: false,
    component: false,
    directive: false,
    // And enable only needed auto-imports
    helpers: true,
    composable: true
  }
});

Then use them directly in SFCs without manual imports:

<script setup lang="ts">
const value = ref('');
const selectedCountry = ref('US');

const { inputRef, setCountry } = usePhoneMask({
  value,
  onChange: (digits) => (value.value = digits),
  country: selectedCountry
});

const setGermany = () => {
  setCountry('DE');
};
</script>

<template>
  <input ref="inputRef" type="tel" />
  <button @click="setGermany">Use Germany</button>
  <p>Code for DE: {{ PMaskHelpers.MasksMap.DE.code }}</p>
</template>

βš™οΈ Configuration

Module Options

Configure the module in nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt'],

  phoneMask: {
    // Import styles automatically
    css: true, // Default: true

    // Register PhoneInput component
    component: true, // Default: true

    // Register v-phone-mask directive
    directive: true, // Default: true

    // Register mask-related TypeScript types
    types: true, // Default: true

    // Register shared helper namespace + directive helper
    helpers: false, // Default: false

    // Register usePhoneMask composable
    composable: false // Default: false
  }
});

Enable Helpers and Composable Auto-imports

export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt'],
  phoneMask: {
    helpers: true,
    composable: true
  }
});

Custom Styling

Option 1: Disable auto CSS import
export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt'],

  phoneMask: {
    css: false // Don't auto-import styles
  }
});

Then import manually where needed:

<style>
@import '@desource/phone-mask-vue/assets/lib.css';

/* Your custom overrides */
.desource-phone-input {
  --pi-border: #your-color;
}
</style>
Option 2: Override CSS variables
<style>
.desource-phone-input,
.desource-phone-dropdown {
  --pi-bg: #f9fafb;
  --pi-border: #e5e7eb;
  --pi-border-focus: #3b82f6;
  --pi-fg: #111827;
}
</style>

πŸ”§ TypeScript

The module provides automatic TypeScript support. You can import Nuxt-registered types from #imports:

import type {
  PCountryKey,
  PMaskBase,
  PMaskBaseMap,
  PMask,
  PMaskMap,
  PMaskWithFlag,
  PMaskWithFlagMap,
  PMaskFull,
  PMaskFullMap,
  PMaskPhoneNumber
} from '#imports';

πŸ“š Examples

Form Integration

<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <label for="name">Name</label>
      <input id="name" v-model="form.name" type="text" />
    </div>

    <div>
      <label for="phone">Phone</label>
      <PhoneInput id="phone" v-model="form.phone" country="US" @validation-change="phoneValid = $event" />
    </div>

    <button type="submit" :disabled="!phoneValid">Submit</button>
  </form>
</template>

<script setup>
const form = reactive({
  name: '',
  phone: ''
});

const phoneValid = ref(false);

const handleSubmit = () => {
  console.log('Form data:', form);
};
</script>

With Pinia Store

// stores/user.ts
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    phoneDigits: '',
    country: 'US'
  }),

  actions: {
    setPhoneDigits(phone: string) {
      this.phoneDigits = phone;
    },

    setCountry(id: string) {
      this.country = id;
    }
  }
});
<template>
  <PhoneInput
    :model-value="userStore.phoneDigits"
    :country="userStore.country"
    @update:model-value="userStore.setPhoneDigits"
    @country-change="userStore.setCountry($event.id)"
  />
</template>

<script setup>
const userStore = useUserStore();
</script>

Multi-step Form

<template>
  <div>
    <div v-if="step === 1">
      <h2>Step 1: Contact Info</h2>
      <PhoneInput v-model="formData.phone" country="US" @validation-change="phoneValid = $event" />
      <button @click="nextStep" :disabled="!phoneValid">Next</button>
    </div>

    <div v-if="step === 2">
      <h2>Step 2: Verification</h2>
      <p>We'll send a code to: {{ formData.phone }}</p>
      <button @click="prevStep">Back</button>
      <button @click="submit">Send Code</button>
    </div>
  </div>
</template>

<script setup>
const step = ref(1);
const phoneValid = ref(false);
const formData = reactive({
  phone: ''
});

const nextStep = () => {
  if (phoneValid.value) step.value++;
};

const prevStep = () => {
  step.value--;
};

const submit = async () => {
  // Send verification code
};
</script>

i18n Integration

<template>
  <PhoneInput v-model="phone" :locale="$i18n.locale" :search-placeholder="$t('phone.searchPlaceholder')" />
</template>

<script setup>
const { locale, t } = useI18n();
const phone = ref('');
</script>

🎯 Auto-imports

By default (without extra options), Nuxt auto-imports:

Components

  • PhoneInput β€” Main phone input component

Directives

  • vPhoneMask β€” Phone mask directive

Types

  • PCountryKey
  • PMaskBase, PMaskBaseMap
  • PMask, PMaskMap
  • PMaskWithFlag, PMaskWithFlagMap
  • PMaskFull, PMaskFullMap
  • PMaskPhoneNumber

Enable phoneMask.helpers: true to auto-import:

  • vPhoneMaskSetCountry β€” Programmatically set country for directive
  • PMaskHelpers β€” Core kit utilities and mask data from @desource/phone-mask-vue/core

Enable phoneMask.composable: true to auto-import:

  • usePhoneMask

πŸ”„ Migration from Vue Plugin

If you're migrating from the Vue plugin:

Before:

// main.ts
import PhoneMaskPlugin from '@desource/phone-mask-vue';
import '@desource/phone-mask-vue/assets/lib.css';

app.use(PhoneMaskPlugin);

After:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@desource/phone-mask-nuxt']
});

No changes needed in your components!

πŸ“¦ What's Included

  • PhoneInput component (auto-imported by default)
  • vPhoneMask directive (auto-imported by default)
  • Mask-related TypeScript types (auto-imported by default)
  • Default styles (auto-imported by default, can be disabled)
  • vPhoneMaskSetCountry and PMaskHelpers (optional via phoneMask.helpers)
  • usePhoneMask (optional via phoneMask.composable)

πŸ”— Related

πŸ“„ License

MIT Β© 2026 DeSource Labs

🀝 Contributing

See Contributing Guide


Related Packages

@desource/phone-mask-vue

πŸ”Œ Vue 3 component, composable, and directive for international phone number mas...

62,996 31
nuxt-maska

Nuxt module for Maska input masking library

6,552 1
nuxt-yup

yup implementation for nuxt

2,981 4
@intl-tel-input/vue

A Vue component for intl-tel-input, for entering, formatting, and validating int...

65,387 8,257
nuxt-rform

Composable form fields for Nuxt: the model, the types and the validation all com...

1,483 1

Version History

Version Released License
1.7.0 MIT
1.6.4 MIT
1.6.3 MIT
1.6.2 MIT
1.6.1 MIT