@desource/phone-mask-nuxt
v1.4.0
Published
π― Zero-config Nuxt module for international phone masking. Powered by @desource/phone-mask with Google libphonenumber sync.
Maintainers
Readme
@desource/phone-mask-nuxt
Nuxt module for phone input with Google's libphonenumber data
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 */
.phone-input {
--pi-border: #your-color;
}
</style>Option 2: Override CSS variables
<style>
:root {
--pi-bg: #f9fafb;
--pi-border: #e5e7eb;
--pi-border-focus: #3b82f6;
--pi-text: #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
PCountryKeyPMaskBase,PMaskBaseMapPMask,PMaskMapPMaskWithFlag,PMaskWithFlagMapPMaskFull,PMaskFullMapPMaskPhoneNumber
Enable phoneMask.helpers: true to auto-import:
vPhoneMaskSetCountryβ Programmatically set country for directivePMaskHelpersβ 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
PhoneInputcomponent (auto-imported by default)vPhoneMaskdirective (auto-imported by default)- Mask-related TypeScript types (auto-imported by default)
- Default styles (auto-imported by default, can be disabled)
vPhoneMaskSetCountryandPMaskHelpers(optional viaphoneMask.helpers)usePhoneMask(optional viaphoneMask.composable)
π Related
- @desource/phone-mask β Core library
- @desource/phone-mask-vue β Vue 3 component
- @desource/phone-mask-react β React bindings
- @desource/phone-mask-svelte β Svelte bindings
π License
MIT Β© 2026 DeSource Labs
