# 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(null); const phoneRef = useRef(null); const handleSubmit = () => { if (isValid) { const fullNumber = phoneRef.current?.getFullFormattedNumber(); console.log('Submitting:', fullNumber); } }; return (
{ console.log('Full:', data.full); // "+12025551234" console.log('Formatted:', data.fullFormatted); // "+1 202-555-1234" console.log('Digits:', data.digits); // "2025551234" }} /> {country &&

Country: {country.flag} {country.name}

} {isValid &&

βœ“ Valid phone number

}
); } ``` ### 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 (
{country.flag} {country.code}

Digits: {digits}

Full: {fullFormatted || 'β€”'}

Status: {isComplete ? 'βœ“ Complete' : 'Incomplete'}

); } ``` ### 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({ defaultValues: { name: '', phone: '' } }); const onSubmit = (data: FormData) => { console.log('Form submitted:', data); }; return (
value.length >= 10 || 'Enter a complete phone number' }} render={({ field }) => ( )} /> {errors.phone && {errors.phone.message}} ); } ``` ## 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 ``` ### v-phone-mask Directive A directive for applying phone masking to custom input elements while maintaining full control over styling. ```vue ``` ### usePhoneMask Composable A Vue composable for complete control over phone input behavior in custom implementations. ```vue ``` ## 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
isValid = v} /> {#if country}

{country.flag} {country.name} ({country.code})

{/if} {#if errorMessage} {errorMessage} {/if}
``` ### phoneMaskAction - Svelte Action A Svelte action for applying phone masking to any input element, compatible with all Svelte 5 versions. ```svelte
{#if phoneData}

Digits: {phoneData.digits}

Full: {phoneData.fullFormatted}

{/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 {#if phoneData}

Formatted: {phoneData.fullFormatted}

{/if} ``` ### usePhoneMask Composable A Svelte composable for building fully custom phone inputs with complete control over the UI. ```svelte

Country: {phoneMask.country.flag} {phoneMask.country.name}

Formatted: {phoneMask.fullFormatted || 'β€”'}

Valid: {phoneMask.isComplete ? 'Yes' : 'No'}

``` ## 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 ``` ### Form Integration with Nuxt Building a complete form with phone validation in Nuxt. ```vue ``` ## 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.