Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Phone Mask
https://github.com/desource-labs/phone-mask
Admin
Phone Mask is an ultra-lightweight international phone number formatter and validator that syncs
...
Tokens:
45,617
Snippets:
303
Trust Score:
7.5
Update:
1 month ago
Context
Skills
Chat
Benchmark
96.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Phone Mask Phone Mask is an ultra-lightweight international phone number formatter and validator that automatically syncs with Google's libphonenumber database. At just 5.1 KB gzipped for the core library, it provides accurate phone formatting for 240+ countries while being significantly smaller than alternatives like libphonenumber-js (43.7 KB) or google-libphonenumber (115.2 KB). The library offers a framework-agnostic TypeScript/JavaScript core along with ready-made integrations for React, Vue 3, Svelte 5, and Nuxt. The library features real-time auto-formatting as users type with smart cursor positioning, built-in validation with visual feedback, country auto-detection via GeoIP and browser locale, a searchable country selector with fuzzy matching and keyboard navigation, copy-to-clipboard functionality, and full theming support with light/dark modes. All packages are fully tree-shakeable, meaning applications only bundle the features they actually use. ## Core Library (@desource/phone-mask) ### Installation ```bash npm install @desource/phone-mask ``` ### MasksFullMapEn - Access Country Data with English Names Provides a map of all country phone masks with English localized names, dialing codes, flag emojis, and mask patterns for formatting. ```typescript import { MasksFullMapEn, type MaskFull } from '@desource/phone-mask'; // Access specific country data const us = MasksFullMapEn.US; console.log(us.name); // "United States" console.log(us.code); // "+1" console.log(us.mask); // ["###-###-####"] console.log(us.flag); // "πΊπΈ" // Countries with multiple mask formats const gb = MasksFullMapEn.GB; console.log(gb.mask); // ["### ### ####", "#### ######", "## #### ####"] // Access by country ISO code (240+ countries) const de = MasksFullMapEn.DE; // Germany const fr = MasksFullMapEn.FR; // France const jp = MasksFullMapEn.JP; // Japan ``` ### MasksFullMap - Localized Country Names Returns country data with names localized to any supported language using the browser's Intl.DisplayNames API. ```typescript import { MasksFullMap } from '@desource/phone-mask'; // Get country names in German const germanMap = MasksFullMap('de'); console.log(germanMap.US.name); // "Vereinigte Staaten" console.log(germanMap.DE.name); // "Deutschland" // Get country names in French const frenchMap = MasksFullMap('fr'); console.log(frenchMap.US.name); // "Γtats-Unis" console.log(frenchMap.JP.name); // "Japon" // Get country names in Spanish const spanishMap = MasksFullMap('es'); console.log(spanishMap.MX.name); // "MΓ©xico" ``` ### MasksBaseMap - Masks with Country Code Prefix Provides mask patterns that include the country code prefix, useful for displaying complete international phone numbers. ```typescript import { MasksBaseMap } from '@desource/phone-mask'; // Get US mask with country code included const usMaskWithPrefix = MasksBaseMap.US; console.log(usMaskWithPrefix); // ["+1 ###-###-####"] // UK masks with country code const gbMaskWithPrefix = MasksBaseMap.GB; console.log(gbMaskWithPrefix); // ["+44 ### ### ####", "+44 #### ######", "+44 ## #### ####"] ``` ### MasksMap - Masks with Separate Country Code Provides mask patterns without the country code prefix, with the dialing code as a separate property. ```typescript import { MasksMap } from '@desource/phone-mask'; // Get US mask data const us = MasksMap.US; console.log(us.code); // "+1" console.log(us.mask); // ["###-###-####"] // Get Germany mask data const de = MasksMap.DE; console.log(de.code); // "+49" console.log(de.mask); // ["### #######", "#### #######", "##### ######"] ``` ### formatDigitsWithMap - Format Phone Digits Formats raw digit strings according to a mask template, returning both the formatted display string and a position map for cursor tracking. ```typescript import { MasksMap, MasksFullMapEn, formatDigitsWithMap } from '@desource/phone-mask'; // Basic formatting const mask = MasksMap.US.mask[0]; // "###-###-####" const result = formatDigitsWithMap(mask, '2025551234'); console.log(result.display); // "202-555-1234" console.log(result.map); // [0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, 9] // Map shows digit index at each position (-1 = separator) // Format with country code prefix const prefixMask = `${MasksFullMapEn.US.code} ${mask}`; // "+1 ###-###-####" const fullResult = formatDigitsWithMap(prefixMask, '12025551234'); console.log(fullResult.display); // "+1 202-555-1234" // Partial input formatting const partial = formatDigitsWithMap(mask, '202555'); console.log(partial.display); // "202-555" ``` ### countPlaceholders - Count Mask Digit Slots Counts the number of digit placeholder (#) characters in a mask string, useful for validation. ```typescript import { countPlaceholders, MasksMap } from '@desource/phone-mask'; // Count digits expected for US number const usCount = countPlaceholders('+1 ###-###-####'); console.log(usCount); // 10 // Validate phone number length function isValidLength(digits: string, countryCode: string): boolean { const masks = MasksMap[countryCode]?.mask || []; const validLengths = masks.map(m => countPlaceholders(m)); return validLengths.includes(digits.length); } console.log(isValidLength('2025551234', 'US')); // true (10 digits) console.log(isValidLength('202555', 'US')); // false (6 digits) ``` ### pickMaskVariant - Select Best Mask for Input Selects the most appropriate mask variant when a country has multiple formats, based on the number of digits entered. ```typescript import { pickMaskVariant, MasksMap } from '@desource/phone-mask'; // UK has multiple mask variants const ukMasks = MasksMap.GB.mask; // ["### ### ####", "#### ######", "## #### ####"] // Pick best mask for 10 digits const mask10 = pickMaskVariant(ukMasks, 10); console.log(mask10); // "### ### ####" (10 placeholders) // Pick best mask for 11 digits const mask11 = pickMaskVariant(ukMasks, 11); console.log(mask11); // "#### ######" (10 placeholders - closest fit) // Pick best mask for 12 digits const mask12 = pickMaskVariant(ukMasks, 12); console.log(mask12); // "## #### ####" (10 placeholders) ``` ### removeCountryCodePrefix - Strip Country Code from Mask Removes the country code prefix from a mask string, converting full international masks to local format. ```typescript import { removeCountryCodePrefix } from '@desource/phone-mask'; // Remove country code from mask const localMask = removeCountryCodePrefix('+1 ###-###-####'); console.log(localMask); // "###-###-####" const ukLocal = removeCountryCodePrefix('+44 #### ######'); console.log(ukLocal); // "#### ######" ``` ### getFlagEmoji - Get Country Flag Emoji Converts an ISO 3166-1 alpha-2 country code to its corresponding flag emoji. ```typescript import { getFlagEmoji } from '@desource/phone-mask'; console.log(getFlagEmoji('US')); // "πΊπΈ" console.log(getFlagEmoji('GB')); // "π¬π§" console.log(getFlagEmoji('DE')); // "π©πͺ" console.log(getFlagEmoji('JP')); // "π―π΅" console.log(getFlagEmoji('BR')); // "π§π·" ``` ### createPhoneFormatter - Create Formatter Instance Creates a formatter instance for a specific country that provides formatting, validation, and cursor position calculations. ```typescript import { createPhoneFormatter, MasksFullMapEn } from '@desource/phone-mask'; const usFormatter = createPhoneFormatter(MasksFullMapEn.US); // Format digits console.log(usFormatter.formatDisplay('2025551234')); // "202-555-1234" // Get maximum digits allowed console.log(usFormatter.getMaxDigits()); // 10 // Get placeholder for input console.log(usFormatter.getPlaceholder()); // "###-###-####" // Check if number is complete console.log(usFormatter.isComplete('2025551234')); // true console.log(usFormatter.isComplete('202555')); // false // Get caret position for cursor placement console.log(usFormatter.getCaretPosition(3)); // 4 (after "202-") console.log(usFormatter.getCaretPosition(6)); // 8 (after "202-555-") ``` ### MasksFullEn & MasksFull - Country Arrays Provides country data as arrays instead of maps, useful for iteration and building country selectors. ```typescript import { MasksFullEn, MasksFull, type MaskFull } from '@desource/phone-mask'; // Build a country selector dropdown function getCountryOptions(): Array<{ id: string; label: string; code: string }> { return MasksFullEn.map(country => ({ id: country.id, label: `${country.flag} ${country.name}`, code: country.code })); } const options = getCountryOptions(); // [ // { id: 'AC', label: 'π¦π¨ Ascension Island', code: '+247' }, // { id: 'AD', label: 'π¦π© Andorra', code: '+376' }, // ...240+ countries // ] // Localized country list const germanCountries = MasksFull('de'); console.log(germanCountries.find(c => c.id === 'US')?.name); // "Vereinigte Staaten" ``` ## React Integration (@desource/phone-mask-react) ### Installation ```bash npm install @desource/phone-mask-react ``` ### PhoneInput Component A fully-featured phone input component with country selector, auto-formatting, validation, and theming support. ```tsx import { useState, useRef } from 'react'; import { PhoneInput, type PhoneInputRef, type MaskFull } from '@desource/phone-mask-react'; import '@desource/phone-mask-react/assets/lib.css'; function ContactForm() { const [phone, setPhone] = useState(''); const [isValid, setIsValid] = useState(false); const [country, setCountry] = useState<MaskFull | null>(null); const phoneRef = useRef<PhoneInputRef>(null); const handleSubmit = () => { if (isValid) { const fullNumber = phoneRef.current?.getFullFormattedNumber(); console.log('Submitting:', fullNumber); } }; return ( <div> <PhoneInput ref={phoneRef} value={phone} onChange={setPhone} country="US" detect={true} theme="auto" size="normal" showCopy={true} showClear={true} withValidity={true} onValidationChange={setIsValid} onCountryChange={setCountry} onPhoneChange={(data) => { console.log('Full:', data.full); // "+12025551234" console.log('Formatted:', data.fullFormatted); // "+1 202-555-1234" console.log('Digits:', data.digits); // "2025551234" }} /> {country && <p>Country: {country.flag} {country.name}</p>} {isValid && <p>β Valid phone number</p>} <button onClick={handleSubmit} disabled={!isValid}> Submit </button> <button onClick={() => phoneRef.current?.clear()}> Clear </button> <button onClick={() => phoneRef.current?.selectCountry('GB')}> Switch to UK </button> </div> ); } ``` ### usePhoneMask Hook A React hook for building custom phone input UIs with full control over rendering while retaining formatting logic. ```tsx import { useState } from 'react'; import { usePhoneMask } from '@desource/phone-mask-react'; function CustomPhoneInput() { const [value, setValue] = useState(''); const { ref, digits, full, fullFormatted, isComplete, isEmpty, country, setCountry, clear } = usePhoneMask({ value, onChange: setValue, country: 'US', detect: true, onPhoneChange: (phone) => { console.log('Phone changed:', phone.fullFormatted); }, onCountryChange: (newCountry) => { console.log('Country changed to:', newCountry.name); } }); return ( <div className="custom-phone-input"> <span className="country-code">{country.flag} {country.code}</span> <input ref={ref} type="tel" placeholder={country.mask[0]} className={isComplete ? 'valid' : isEmpty ? '' : 'invalid'} /> <div className="info"> <p>Digits: {digits}</p> <p>Full: {fullFormatted || 'β'}</p> <p>Status: {isComplete ? 'β Complete' : 'Incomplete'}</p> </div> <div className="actions"> <button onClick={() => setCountry('DE')}>Germany</button> <button onClick={() => setCountry('JP')}>Japan</button> <button onClick={clear}>Clear</button> </div> </div> ); } ``` ### React Hook Form Integration Integrating PhoneInput with React Hook Form for form validation and submission. ```tsx import { useForm, Controller } from 'react-hook-form'; import { PhoneInput } from '@desource/phone-mask-react'; import '@desource/phone-mask-react/assets/lib.css'; interface FormData { name: string; phone: string; } function RegistrationForm() { const { control, handleSubmit, formState: { errors } } = useForm<FormData>({ defaultValues: { name: '', phone: '' } }); const onSubmit = (data: FormData) => { console.log('Form submitted:', data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Controller name="phone" control={control} rules={{ required: 'Phone number is required', validate: (value) => value.length >= 10 || 'Enter a complete phone number' }} render={({ field }) => ( <PhoneInput value={field.value} onChange={field.onChange} onBlur={field.onBlur} country="US" withValidity={true} /> )} /> {errors.phone && <span className="error">{errors.phone.message}</span>} <button type="submit">Register</button> </form> ); } ``` ## Vue 3 Integration (@desource/phone-mask-vue) ### Installation ```bash npm install @desource/phone-mask-vue ``` ### PhoneInput Component A Vue 3 component with v-model support, events, and slots for customization. ```vue <script setup lang="ts"> import { ref, computed } from 'vue'; import { PhoneInput, type PMaskFull, type PMaskPhoneNumber } from '@desource/phone-mask-vue'; import '@desource/phone-mask-vue/assets/lib.css'; const phone = ref(''); const isValid = ref(false); const country = ref<PMaskFull | null>(null); const phoneInputRef = ref<InstanceType<typeof PhoneInput>>(); const statusMessage = computed(() => { if (!phone.value) return ''; return isValid.value ? 'β Valid phone number' : 'Please complete the number'; }); const handleCountryChange = (newCountry: PMaskFull) => { country.value = newCountry; console.log('Country changed to:', newCountry.name); }; const handlePhoneChange = (data: PMaskPhoneNumber) => { console.log('Full number:', data.full); console.log('Formatted:', data.fullFormatted); console.log('Digits only:', data.digits); }; const submitForm = () => { const fullNumber = phoneInputRef.value?.getFullFormattedNumber(); console.log('Submitting:', fullNumber); }; </script> <template> <div class="phone-form"> <PhoneInput ref="phoneInputRef" v-model="phone" country="US" detect theme="auto" size="normal" :show-copy="true" :show-clear="true" :with-validity="true" @change="handlePhoneChange" @country-change="handleCountryChange" @validation-change="isValid = $event" /> <p v-if="country">{{ country.flag }} {{ country.name }} ({{ country.code }})</p> <p :class="{ valid: isValid, invalid: !isValid && phone }">{{ statusMessage }}</p> <div class="actions"> <button @click="submitForm" :disabled="!isValid">Submit</button> <button @click="phoneInputRef?.clear()">Clear</button> <button @click="phoneInputRef?.selectCountry('GB')">Switch to UK</button> </div> </div> </template> ``` ### v-phone-mask Directive A directive for applying phone masking to custom input elements while maintaining full control over styling. ```vue <script setup lang="ts"> import { ref } from 'vue'; import { vPhoneMask, type PMaskPhoneNumber, type PMaskFull } from '@desource/phone-mask-vue'; const selectedCountry = ref('US'); const phoneData = ref<PMaskPhoneNumber>({ full: '', fullFormatted: '', digits: '' }); const handleChange = (phone: PMaskPhoneNumber) => { phoneData.value = phone; console.log('Phone:', phone.fullFormatted); }; const handleCountryChange = (country: PMaskFull) => { console.log('Country:', country.name, country.code); }; </script> <template> <div class="custom-phone-input"> <select v-model="selectedCountry" class="country-select"> <option value="US">πΊπΈ United States (+1)</option> <option value="GB">π¬π§ United Kingdom (+44)</option> <option value="DE">π©πͺ Germany (+49)</option> <option value="FR">π«π· France (+33)</option> <option value="JP">π―π΅ Japan (+81)</option> </select> <input v-phone-mask="{ country: selectedCountry, onChange: handleChange, onCountryChange: handleCountryChange }" type="tel" placeholder="Enter phone number" class="phone-input" /> <div class="output"> <p>Digits: {{ phoneData.digits }}</p> <p>Full: {{ phoneData.fullFormatted || 'β' }}</p> </div> </div> </template> ``` ### usePhoneMask Composable A Vue composable for complete control over phone input behavior in custom implementations. ```vue <script setup lang="ts"> import { ref } from 'vue'; import { usePhoneMask, type PMaskPhoneNumber } from '@desource/phone-mask-vue'; const value = ref(''); const { inputRef, digits, full, fullFormatted, isComplete, isEmpty, country, setCountry, clear } = usePhoneMask({ value, onChange: (newValue) => value.value = newValue, country: 'US', detect: false, onPhoneChange: (phone: PMaskPhoneNumber) => { console.log('Phone updated:', phone.fullFormatted); } }); </script> <template> <div class="phone-composable"> <div class="input-row"> <span class="prefix">{{ country.flag }} {{ country.code }}</span> <input ref="inputRef" type="tel" placeholder="Phone number" :class="{ valid: isComplete, invalid: !isEmpty && !isComplete }" /> </div> <div class="info"> <p>Country: {{ country.name }}</p> <p>Digits: {{ digits }}</p> <p>Formatted: {{ fullFormatted || 'β' }}</p> <p>Complete: {{ isComplete ? 'Yes' : 'No' }}</p> </div> <div class="controls"> <button @click="setCountry('GB')">UK</button> <button @click="setCountry('DE')">Germany</button> <button @click="setCountry('JP')">Japan</button> <button @click="clear">Clear</button> </div> </div> </template> ``` ## Svelte 5 Integration (@desource/phone-mask-svelte) ### Installation ```bash npm install @desource/phone-mask-svelte ``` ### PhoneInput Component A Svelte 5 component with rune-based reactivity and snippet slots for customization. ```svelte <script lang="ts"> import { PhoneInput, type PhoneInputExposed, type PMaskFull, type PMaskPhoneNumber } from '@desource/phone-mask-svelte'; import '@desource/phone-mask-svelte/assets/lib.css'; let phone = $state(''); let isValid = $state(false); let country = $state<PMaskFull | null>(null); let phoneInput = $state<PhoneInputExposed | null>(null); const errorMessage = $derived( !phone ? '' : isValid ? '' : 'Please enter a complete phone number' ); function handleCountryChange(newCountry: PMaskFull) { country = newCountry; console.log('Country:', newCountry.name, newCountry.code); } function handlePhoneChange(data: PMaskPhoneNumber) { console.log('Full:', data.full); console.log('Formatted:', data.fullFormatted); } function submit() { const fullNumber = phoneInput?.getFullFormattedNumber(); console.log('Submitting:', fullNumber); } </script> <div class="phone-form"> <PhoneInput bind:this={phoneInput} bind:value={phone} country="US" detect theme="auto" size="normal" showCopy={true} showClear={true} withValidity={true} onchange={handlePhoneChange} oncountrychange={handleCountryChange} onvalidationchange={(v) => isValid = v} /> {#if country} <p>{country.flag} {country.name} ({country.code})</p> {/if} {#if errorMessage} <span class="error">{errorMessage}</span> {/if} <div class="actions"> <button onclick={submit} disabled={!isValid}>Submit</button> <button onclick={() => phoneInput?.clear()}>Clear</button> <button onclick={() => phoneInput?.selectCountry('GB')}>Switch to UK</button> </div> </div> ``` ### phoneMaskAction - Svelte Action A Svelte action for applying phone masking to any input element, compatible with all Svelte 5 versions. ```svelte <script lang="ts"> import { phoneMaskAction as phoneMask } from '@desource/phone-mask-svelte'; import type { PMaskPhoneNumber, PMaskFull } from '@desource/phone-mask-svelte'; let selectedCountry = $state('US'); let phoneData = $state<PMaskPhoneNumber | null>(null); function handleChange(phone: PMaskPhoneNumber) { phoneData = phone; console.log('Phone:', phone.fullFormatted); } function handleCountryChange(country: PMaskFull) { console.log('Country changed to:', country.name); } </script> <div class="custom-input"> <select bind:value={selectedCountry}> <option value="US">πΊπΈ United States (+1)</option> <option value="GB">π¬π§ United Kingdom (+44)</option> <option value="DE">π©πͺ Germany (+49)</option> </select> <input use:phoneMask={{ country: selectedCountry, onChange: handleChange, onCountryChange: handleCountryChange }} type="tel" placeholder="Phone number" /> <!-- Simple shorthand for country-only --> <input use:phoneMask={'US'} placeholder="US number" /> <!-- With auto-detection --> <input use:phoneMask={{ detect: true, onChange: handleChange }} placeholder="Auto-detect" /> </div> {#if phoneData} <div class="output"> <p>Digits: {phoneData.digits}</p> <p>Full: {phoneData.fullFormatted}</p> </div> {/if} ``` ### phoneMaskAttachment - Svelte 5.29+ Attachment A Svelte attachment for phone masking that provides automatic reactivity without manual update hooks (requires Svelte 5.29+). ```svelte <script lang="ts"> import { phoneMaskAttachment as phoneMask } from '@desource/phone-mask-svelte'; import type { PMaskPhoneNumber } from '@desource/phone-mask-svelte'; let country = $state('US'); let phoneData = $state<PMaskPhoneNumber | null>(null); function handleChange(phone: PMaskPhoneNumber) { phoneData = phone; } </script> <select bind:value={country}> <option value="US">πΊπΈ +1</option> <option value="GB">π¬π§ +44</option> <option value="DE">π©πͺ +49</option> </select> <!-- Attachment automatically re-runs when country changes --> <input {@attach phoneMask({ country, onChange: handleChange })} type="tel" placeholder="Phone number" /> <!-- Shorthand --> <input {@attach phoneMask('US')} placeholder="US only" /> {#if phoneData} <p>Formatted: {phoneData.fullFormatted}</p> {/if} ``` ### usePhoneMask Composable A Svelte composable for building fully custom phone inputs with complete control over the UI. ```svelte <script lang="ts"> import { usePhoneMask, type PMaskPhoneNumber } from '@desource/phone-mask-svelte'; let inputValue = $state(''); let selectedCountry = $state('US'); // Note: Do NOT destructure - all properties are reactive getters const phoneMask = usePhoneMask({ value: () => inputValue, country: () => selectedCountry, detect: () => false, onChange: (digits) => { inputValue = digits; }, onPhoneChange: (data: PMaskPhoneNumber) => { console.log('Phone:', data.fullFormatted); } }); </script> <div class="custom-phone"> <select bind:value={selectedCountry}> <option value="US">πΊπΈ +1</option> <option value="GB">π¬π§ +44</option> <option value="DE">π©πͺ +49</option> </select> <input bind:this={phoneMask.inputRef} type="tel" placeholder="Phone number" /> <div class="info"> <p>Country: {phoneMask.country.flag} {phoneMask.country.name}</p> <p>Formatted: {phoneMask.fullFormatted || 'β'}</p> <p>Valid: {phoneMask.isComplete ? 'Yes' : 'No'}</p> </div> <button onclick={() => phoneMask.setCountry('JP')}>Japan</button> <button onclick={() => phoneMask.clear()}>Clear</button> </div> ``` ## Nuxt Integration (@desource/phone-mask-nuxt) ### Installation ```bash npm install @desource/phone-mask-nuxt ``` ### Module Setup Zero-config Nuxt module that auto-imports the component, directive, and helpers. ```typescript // nuxt.config.ts export default defineNuxtConfig({ modules: ['@desource/phone-mask-nuxt'], // Optional configuration phoneMask: { css: true, // Auto-import styles (default: true) component: true, // Register PhoneInput component (default: true) directive: true, // Register v-phone-mask directive (default: true) helpers: true // Register utility helpers (default: true) } }); ``` ### Using PhoneInput in Nuxt The component is automatically available without imports. ```vue <script setup lang="ts"> import type { PMaskPhoneNumber, PMaskFull } from '#imports'; const phone = ref(''); const isValid = ref(false); const handlePhoneChange = (data: PMaskPhoneNumber) => { console.log('Full:', data.fullFormatted); }; const handleCountryChange = (country: PMaskFull) => { console.log('Country:', country.name); }; </script> <template> <div> <PhoneInput v-model="phone" country="US" detect @change="handlePhoneChange" @country-change="handleCountryChange" @validation-change="isValid = $event" /> <p v-if="isValid">β Valid phone number</p> </div> </template> ``` ### Form Integration with Nuxt Building a complete form with phone validation in Nuxt. ```vue <script setup lang="ts"> import type { PMaskPhoneNumber } from '#imports'; const form = reactive({ name: '', email: '', phone: '' }); const phoneValid = ref(false); const fullPhone = ref(''); const handlePhoneChange = (data: PMaskPhoneNumber) => { fullPhone.value = data.full; }; const handleSubmit = async () => { if (!phoneValid.value) return; await $fetch('/api/contact', { method: 'POST', body: { ...form, phone: fullPhone.value // Send full international format } }); }; </script> <template> <form @submit.prevent="handleSubmit"> <div> <label for="name">Name</label> <input id="name" v-model="form.name" required /> </div> <div> <label for="email">Email</label> <input id="email" v-model="form.email" type="email" required /> </div> <div> <label for="phone">Phone</label> <PhoneInput id="phone" v-model="form.phone" country="US" detect @change="handlePhoneChange" @validation-change="phoneValid = $event" /> </div> <button type="submit" :disabled="!phoneValid"> Submit </button> </form> </template> ``` ## CSS Customization ### Theme Variables All components support CSS variable customization for colors, sizes, and other visual properties. ```css /* Custom theme overrides */ .phone-input, .phone-dropdown { /* Colors */ --pi-bg: #ffffff; --pi-fg: #111827; --pi-muted: #6b7280; --pi-border: #e5e7eb; --pi-border-hover: #d1d5db; --pi-border-focus: #3b82f6; --pi-focus-ring: 3px solid rgb(59 130 246 / 0.15); --pi-disabled-bg: #f9fafb; --pi-disabled-fg: #9ca3af; /* Sizes */ --pi-font-size: 16px; --pi-height: 44px; --pi-padding: 12px; --pi-radius: 8px; /* Shadows */ --pi-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --pi-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); /* Validation */ --pi-warning: #f59e0b; --pi-success: #10b981; --pi-focus-ring-warning: 3px solid rgb(245 158 11 / 0.15); --pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.15); } /* Dark theme overrides */ .phone-input[data-theme='dark'], .phone-dropdown[data-theme='dark'] { --pi-bg: #1f2937; --pi-fg: #f9fafb; --pi-muted: #9ca3af; --pi-border: #374151; --pi-border-hover: #4b5563; --pi-disabled-bg: #111827; } ``` ## Summary Phone Mask provides a comprehensive solution for international phone number input across all major JavaScript frameworks. The core library offers lightweight utilities for phone number formatting, validation, and country data access, while framework-specific packages provide ready-to-use components with features like auto-formatting, country detection, fuzzy search, and built-in validation. The modular architecture allows developers to use just the core utilities for custom implementations or full-featured components for rapid development. Integration patterns include using the component directly with v-model/bind:value for quick setup, the directive/action/attachment approach for custom-styled inputs, and the composable/hook API for complete UI control. All packages share consistent APIs and behavior, making it easy to maintain codebases that span multiple frameworks. The automatic weekly sync with Google's libphonenumber ensures phone formats stay current without manual updates, while the small bundle size and tree-shakeability make it suitable for performance-critical applications.