v-phone-input
Version:
International phone field for Vuetify 3 and Vue 3.
738 lines (546 loc) • 31.1 kB
Markdown
# VPhoneInput
[](https://github.com/paul-thebaud/v-phone-input/actions/workflows/tests.yml)
[](https://github.com/paul-thebaud/v-phone-input/actions/workflows/publish.yml)
[](https://codecov.io/gh/paul-thebaud/v-phone-input)
[](https://www.npmjs.com/package/v-phone-input)


International phone field for [Vuetify 3](http://vuetifyjs.com) and [Vue 3](https://vuejs.org/).
- Simple and standardized value using E164 formatted phone numbers (example: +33123456789)
- [Searchable countries](#enabling-searching-countries)
- [Automatic validation](#validation)
- [Customizable display format](#customizing-display-format)
- [Easy localization with label functions](#localization)
- [Customizable countries icons](#country-icon-modes)
- [Relies on external packages](#dependencies) to provide countries data and icons
- Built-in types definitions with Typescript
- Fully unit/end-to-end tested
**Coming from 2.x.x and upgrading to Vuetify 3?**
Checkout [the migration guide](MIGRATION.md).
**Wish to use this package with Vuetify 2?**
[Old version 2.x.x](https://github.com/paul-thebaud/v-phone-input/tree/2.x.x)
is compatible with Vuetify 2 and Vue 2.
Proudly supported by the [CoWork'HIT](https://coworkhit.com).
**Motivation:** There are already multiple libraries to provide phone number input on Vuetify. But
for those already published are not actively maintained or does not put focus on providing great
accessibility. This new library aims to provide those two.
## Demo
You can try VPhoneInput with many options and plugin registration code generation on
the [GitHub pages demo](https://paul-thebaud.github.io/v-phone-input/).
You can also use
the [Stackblitz playground](https://stackblitz.com/edit/v-phone-input?file=src%2FApp.vue&terminal=dev)
to try the package or prepare bug reproductions.
## TL;DR
Installation though Yarn:
```shell
yarn add v-phone-input flag-icons
```
Installation though NPM:
```shell
npm install v-phone-input flag-icons
```
Plugin installation:
```javascript
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { createVPhoneInput } from 'v-phone-input';
const vPhoneInput = createVPhoneInput({
countryIconMode: 'svg',
});
app.use(vPhoneInput);
```
> If you are using Nuxt, check out the [Nuxt setup guide](#nuxt-setup).
Component usage:
```vue
<script setup>
import { ref } from 'vue';
const phone = ref('');
</script>
<template>
<v-phone-input v-model="phone" />
</template>
```
## Documentation
### Table of contents
- [Requirements](#requirements)
- [Installation](#installation)
- [Migration](#migration)
- [Usage](#usage)
- [Nuxt setup](#nuxt-setup)
- [Props](#props)
- [Events](#events)
- [Slots](#slots)
- [Exposed](#exposed)
- [Examples](#examples)
- [Country icon modes](#country-icon-modes)
- [Validation](#validation)
- [Enabling searching countries](#enabling-searching-countries)
- [Customizing display format](#customizing-display-format)
- [Localization](#localization)
- [Dependencies](#dependencies)
- [Types](#types)
- [Country](#country)
- [Country guesser](#country-guesser)
- [Phone number formats](#phone-number-formats)
- [Phone number](#phone-number)
- [Message options](#message-options)
- [Message](#message)
- [Message resolver](#message-resolver)
### Requirements
VPhoneInput requires `Vue@3` and `Vuetify@3` to be installed and working in your project.
> VPhoneInput utilizes recent ES features that may require polyfills for older browser like
> Internet Explorer.
> [Available country guessers](#country-guesser) requires fetch.
### Installation
You can install this package though Yarn:
```shell
yarn add v-phone-input flag-icons
```
Or NPM:
```shell
npm install v-phone-input flag-icons
```
> `flag-icons` package is required if you want the input to display countries' flags.
### Usage
You can globally define the input using the provided plugin factory. This will register
the `v-phone-input` component globally. You must also import the package additional CSS.
```javascript
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { createVPhoneInput } from 'v-phone-input';
import { createApp } from 'vue';
const app = createApp(App);
const vPhoneInput = createVPhoneInput({
countryIconMode: 'svg',
});
app.use(vPhoneInput);
```
You may also only define the field on a per-file basis. Notice that with this method, you won't be
able to define props' default values for the input.
```vue
<script setup>
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { VPhoneInput } from 'v-phone-input';
import { ref } from 'vue';
const phone = ref('');
</script>
<template>
<v-phone-input
v-model="phone"
country-icon-mode="svg"
/>
</template>
```
### Migration
Please follow the [migration guide](MIGRATION.md) if you need to migrate from version 1, 2 or 3.
### Nuxt setup
If you are using Nuxt, you must mark `v-phone-input` as a transpiled package in
your Nuxt configuration:
```typescript
export default defineNuxtConfig({
build: {
transpile: [
// Keep other transpile configuration, such as:
'vuetify',
// Add transpile for the package:
'v-phone-input',
],
},
});
```
Instead of defining a Vue plugin, you must define a Nuxt plugin as follows:
```typescript
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { createVPhoneInput } from 'v-phone-input';
export default defineNuxtPlugin((nuxtApp) => {
const vPhoneInput = createVPhoneInput({
countryIconMode: 'svg',
});
nuxtApp.vueApp.use(vPhoneInput);
});
```
### Props
VPhoneInput provides many props to customize the input behaviors or display.
You may pass those props directly the input:
```vue
<template>
<v-phone-input label="Your Phone number" />
</template>
```
Or define them as default values when creating the plugin:
```javascript
import 'v-phone-input/dist/v-phone-input.css';
import { createVPhoneInput } from 'v-phone-input';
import { createApp } from 'vue';
const app = createApp(App);
const vPhoneInput = createVPhoneInput({
label: 'Your phone number',
});
app.use(vPhoneInput);
```
| Name | Type | Default | Description |
|--------------------------|--------------------------------------------------------------------|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| `label` | [`MessageResolver`](#message-resolver) | Phone | The phone input label (see [Localization](#localization)). |
| `ariaLabel` | [`MessageResolver`](#message-resolver) | `undefined` | The phone input `aria-label` (see [Localization](#localization)). |
| `countryLabel` | [`MessageResolver`](#message-resolver) | Country | The country input label (see [Localization](#localization)). |
| `countryAriaLabel` | [`MessageResolver`](#message-resolver) | Country for {label} | The phone input `aria-label` (see [Localization](#localization)). |
| `placeholder` | [`MessageResolver`](#message-resolver) | `undefined` | The phone input placeholder (see [Localization](#localization)). |
| `hint` | [`MessageResolver`](#message-resolver) | `undefined` | The phone input hint (see [Localization](#localization)). |
| `invalidMessage` | [`MessageResolver`](#message-resolver) or `null` | The "{label}" field is not a valid phone number (example: {example}). | The phone input message when number is invalid (see [Localization](#localization)). You can pass `null` to disable default validation. |
| `example` | `string` or `Function` or `undefined` | `undefined` | Example of a valid phone number (or factory function which takes a [`Country[]`](#country) object) to customize phone number example for message. |
| `countryIconMode` | `string` or `VueConstructor` or `undefined` | `undefined` | The country icon display mode (see [Country icon modes](#country-icon-modes)). |
| `allCountries` | [`Country[]`](#country) | An array of all possible countries | Array of countries to use. |
| `preferCountries` | [`CountryOrIso2[]`](#country) | `[]` | Array of countries' codes to propose as first option of country input. |
| `includeCountries` | [`CountryOrIso2[]`](#country) | `[]` | Array of countries' codes to include as options of country input. |
| `excludeCountries` | [`CountryOrIso2[]`](#country) | `[]` | Array of countries' codes to exclude from options of country input. |
| `defaultCountry` | [`CountryOrIso2`](#country) | `undefined` | Default country to select when not guessing nor detecting from current value. |
| `countryGuesser` | [`CountryGuesser` or `PreferableCountryGuesser`](#country-guesser) | `new MemoIp2cCountryGuesser()` | Country guesser implementation to use when guessing country (see [Country guesser](#country-guesser)). |
| `guessCountry` | `boolean` | `false` | Enable guessing country using default or provided country guesser. |
| `disableGuessLoading` | `boolean` | `false` | Disable passing the country input in a loading state when guessing country. |
| `enableSearchingCountry` | `boolean` | `false` | Turns the country input into a `VAutocomplete` input (see [Enabling searching countries example](#enabling-searching-countries)). |
| `rules` | `Function[]` or `string[]` | `[]` | Additional rules to pass to phone input (see [Validation example](#validation)). |
| `displayFormat` | `PhoneNumberFormat` | `'national'` | Format to use when displaying valid phone numbers in phone text input (see [Phone number formats](#phone-number-formats)). |
| `country` | `string` | `''` | Country of the country input. Can be used with `.sync` modifier. Will be superseded by value's country if defined on mounting. |
| `value` | `string` | `''` | Value of the phone input. You may use it using `v-model` or `@input`. |
| `wrapperProps` | `object` | `{}` | Props to pass to the inputs wrapper `div` element. |
| `countryProps` | `object` | `{}` | Props to pass to the `VSelect` or `VAutocomplete` country input component. |
| `phoneProps` | `object` | `{}` | Props to pass to the `VTextField` phone input component. |
| `phoneValidator` | `(phone: ParsedPhoneNumber) => boolean` | `(phone) => phone.valid` | Callback to check the phone validity (see [Custom phone number validator](#custom-phone-number-validator)). |
#### Props inheritance
You can also pass the [Vuetify `VTextField`](https://vuetifyjs.com/en/api/v-text-field/#props)
and [Vuetify `VSelect`](https://vuetifyjs.com/en/api/v-select/#props)
props to the component to customize variant, icons, errors, etc. using the `v-bind` directive or
the `countryProps` and `phoneProps` props.
Most props will only apply to the phone input component (not wrapper `div` or country select),
but be aware that:
- Some props will only apply to the inputs wrapper `div` element: `id`, `class` and `style`.
- Some props are applied to both inputs: `variant`, `flat`, `tile`, `density`, `singleLine`,
`hideDetails`, `direction`, `reverse`, `color`, `bgColor`, `theme`, `disabled`, `readonly`, and
`rounded`.
### Events
| Name | Type | Description |
|---------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| `update:modelValue` | `string` | Emitted when the country or phone is updated with the E164 formatted phone number. If the input is not valid, the raw input value will be emitted. |
| `update:country` | `string` | Emitted when the country is updated with the selected country. |
> You can also bind to the [Vuetify `VTextField`](https://vuetifyjs.com/en/api/v-text-field/#props)
> and [Vuetify `VSelect`](https://vuetifyjs.com/en/api/v-select/#props) events
> using the `v-bind` directive or the `countryProps` and `phoneProps` props.
### Slots
Each slots with a `country:` prefix are passed to the country input, other slots are passed to the
phone input.
```vue
<template>
<v-phone-input>
<template #country:label>
Label for country
</template>
<template #label>
Label for phone
</template>
</v-phone-input>
</template>
```
The input also provides 5 special slots:
- `country-selection` for countries' display in selection.
- `country-icon` for countries' icons display in selection and select items.
- `country-name` for countries' name display in select items.
- `country-append` for countries' appended info display in select items.
- `country-input` to override the country input.
- `phone-input` to override the country input.
Appart from `country-input` and `phone-input`, each of those slots will receive
a [`country`](#country) object property.
`country-icon` slot will also receive a `decorative` boolean property,
which will be `false` inside selection, and `true` inside select item.
```vue
<template>
<v-phone-input>
<template #country-icon="{ country, decorative }">
<img
:src="`path/to/flags/${country.iso2}.png`"
:alt="decorative ? '' : country.name"
>
</template>
<template #country-name="{ country }">
{{ country.name }}
</template>
<template #country-append="{ country }">
<strong>+{{ country.dialCode }}</strong>
</template>
</v-phone-input>
</template>
```
### Exposed
| Name | Type | Description |
|-------------------|----------------------------------------|-------------------------------------------------------------------|
| `countryInputRef` | `Ref<VSelect>` or `Ref<VAutocomplete>` | Reference to the country input. |
| `phoneInputRef` | `Ref<VTextField>` | Reference to the phone input. |
| `errorMessages` | `string[]` | An array of error messages that were set by the setErrors method. |
| `isValid` | `boolean` | Boolean indicating if the input is valid. |
| `reset` | `() => Promise<void>` | Resets the phone input value. |
| `resetValidation` | `() => Promise<void>` | Resets validation of the phone input without modifying its value. |
| `validate` | `(silent: boolean) => Promise<void>` | Validates the phone input’s value. |
### Examples
#### Country icon modes
With VPhoneInput, you can choose between 5 country icon modes which are changing the way the country
input will display.
##### SVG
This is the proposed way to use the input. Rely on an SVG flag icons package. You must
install [`flag-icons`](https://www.npmjs.com/package/flag-icons) package to use it.
```javascript
import 'flag-icons/css/flag-icons.min.css';
const vPhoneInput = createVPhoneInput({
countryIconMode: 'svg',
});
```
##### Sprite
Rely on a CSS sprite flag icons package. You must
install [`world-flags-sprite`](https://www.npmjs.com/package/world-flags-sprite) package to use it.
```javascript
import 'world-flags-sprite/stylesheets/flags32.css';
const vPhoneInput = createVPhoneInput({
countryIconMode: 'sprite',
});
```
##### Custom component
This allows you to register a custom component to display country icons. Component will
receive `country` and `decorative` props. We provide a simple `VPhoneCountrySpan` component to
simplify using a CSS class image based icon system (such as another CSS sprite file).
```javascript
import { defineComponent, h } from 'vue';
const vPhoneInput = createVPhoneInput({
countryIconMode: defineComponent({
setup(props) {
return () => h('span', {}, [props.country.name]);
},
}),
});
```
##### Custom slot
See the [slots section](#slots).
##### No icon
This is the default behavior when not overriding options or props default values. This will not
display an icon inside the list, but will show the ISO-2 code inside the selection slot of country
input.
#### Validation
By default, the input will validate that the phone number is a valid one by
injecting a rule to the phone text input.
You may add any additional rules by providing a `rules` prop to the input:
```vue
<script setup>
const rules = [
(value, phone, { label, country, example }) => !!value || `The "${label}" field is required.`,
];
</script>
<template>
<v-phone-input :rules="rules" />
</template>
```
Any rule you pass as a function will receive 3 arguments (instead of one for default Vuetify rules)
that you may use when validating user's input:
- `value`: the value contained in the phone text input.
- `phone`:
the [parsed phone number object created by awesome-phonenumber](https://github.com/grantila/awesome-phonenumber#basic-usage).
- `messageOptions`: the [message options](#message-options) which you may use to inject the input
label, current country or a phone example inside the message.
##### Disabling default validation
If you don't want the automatic validation to run, you can pass a `null` value to the
`invalid-message` prop:
```vue
<template>
<v-phone-input :invalid-message="null" />
</template>
```
##### Custom phone number validator
By default, VPhoneInput will only consider "classic" phone numbers to be valid.
If you want to customize this behavior, for example to allow emergency short
numbers too, you can configure a `phoneValidator` callback. In the following
example, both classic and short numbers are considered valid. The phone
value given to the callback is
[a parsed phone number object created by awesome-phonenumber](https://github.com/grantila/awesome-phonenumber#basic-usage).
```vue
<template>
<v-phone-input
:phone-validator="(phone) => phone.valid || phone.shortValid"
/>
</template>
```
#### Enabling searching countries
You may provide a `enableSearchingCountry` with a `true` value to enable textual search in
countries.
> Since VPhoneInput does not import VAutocomplete to reduce its weight, you might need to provide
> this component to Vue when treeshaking Vuetify components
> (e.g. when using `vite-plugin-vuetify`).
##### When using plugin registration
To enable searching countries for all inputs as a default behavior, you must
register the `VAutocomplete` component globally inside your plugin definition.
```javascript
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { createVPhoneInput } from 'v-phone-input';
import { VAutocomplete } from 'vuetify/components';
// ...your Vue app creation.
// IMPORTANT: required when treeshaking Vuetify components.
app.component('VAutocomplete', VAutocomplete);
const vPhoneInput = createVPhoneInput({
enableSearchingCountry: true,
});
app.use(vPhoneInput);
```
After this setup, all `v-phone-input` will be using an autocomplete input
for country by default.
##### When using per-file registration
To enable searching countries on a per-file basis, you must register the
`VAutocomplete` component inside your app definition.
```javascript
import { VAutocomplete } from 'vuetify/components';
// ...your Vue app creation.
// IMPORTANT: required when treeshaking Vuetify components.
app.component('VAutocomplete', VAutocomplete);
```
After this setup, you can safely enable the `enable-searching-country` property.
```vue
<script setup>
import 'flag-icons/css/flag-icons.min.css';
import 'v-phone-input/dist/v-phone-input.css';
import { VPhoneInput } from 'v-phone-input';
</script>
<template>
<v-phone-input enable-searching-country />
</template>
```
#### Customizing display format
By default, valid phone number will be formatted using the national format. You can customize the
display format using the `displayFormat` prop/option:
```javascript
const vPhoneInput = createVPhoneInput({
displayFormat: 'international',
});
```
#### Localization
Localizable props may be defined on a per-input basis:
```vue
<template>
<v-phone-input
label="Phone number"
country-label="Country"
country-aria-label="Country for phone number"
invalid-message="Phone number must be a valid phone number (example: 01 23 45 67 89)."
/>
</template>
```
Localizable props can also be defined for all inputs as a default behavior:
```javascript
// Example without any localization library.
const vPhoneInput = createVPhoneInput({
label: 'Phone number',
countryLabel: 'Country',
countryAriaLabel: ({ label }) => `Country for ${label}`,
invalidMessage: ({ label, example }) =>
`${label} must be a valid phone number (example: ${example}).`,
});
// Example with Vue-I18N localization library.
import i18n from './path/to/i18n-plugin';
const vPhoneInput = createVPhoneInput({
label: i18n.global.t('phone.phoneLabel'),
countryLabel: i18n.global.t('phone.phoneCountry'),
countryAriaLabel: (options) => i18n.global.t('phone.phoneCountryFor', options),
invalidMessage: (options) => i18n.global.t('phone.invalidPhoneGiven', options),
});
```
> Any localizable prop is a [message resolver](#message-resolver). Notice that for `label`
> and `ariaLabel` props, no label will be defined for the message resolver's options.
> To disable a message, you should pass `null` (instead of `undefined`). This way, you'll be able
> to disable the country label for example (be sure to provide an explicit `countryAriaLabel`
> when doing so).
### Dependencies
VPhoneInput relies on multiple dependencies to work:
- [`awesome-phonenumber`](https://www.npmjs.com/package/awesome-phonenumber) for phone number
formats, validation, etc.
- [`countries-list`](https://www.npmjs.com/package/countries-list) for phone number
countries' names.
- [`flag-icons`](https://www.npmjs.com/package/flag-icons) when using the `svg` country icon mode
for SVG country flags.
- [`world-flags-sprite`](https://www.npmjs.com/package/world-flags-sprite) when using the `sprite`
country icon mode for CSS sprite country flags.
### Types
#### Country
A country object contains information about a country.
```typescript
interface Country {
name: string; // Example: "France".
iso2: string; // Example: "FR".
dialCode: string; // Example: "33".
}
export type CountryOrIso2 = Country | string;
```
#### Country Guesser
A country guesser is a class implementing `CountryGuesser` interface and providing a `guess` method
to detect the default country to use.
```typescript
interface CountryGuesser {
guess: () => Promise<string | undefined>;
}
```
This package ships with two `CountryGuesser` implementations:
- `Ip2cCountryGuesser` which uses [IP2C service](https://ip2c.org) to guess the country from the
client's IP. Notice that IP2C service might not work when using an add blocking extension.
- `MemoIp2cCountryGuesser` (default) which memoize the result of `Ip2cCountryGuesser` promise into a
class property.
- `StorageMemoIp2cCountryGuesser` which memoize the result of `Ip2cCountryGuesser` promise into a
storage implementation (defaults to `localStorage`).
A preferable country guesser is a country guesser with the capability to use the user preference
instead of the guessed country on future calls.
```typescript
interface PreferableCountryGuesser extends CountryGuesser {
setPreference: (country: string) => void;
}
```
`MemoIp2cCountryGuesser` and `StorageMemoIp2cCountryGuesser` are implementations of
the `PreferableCountryGuesser` interface. They store the country (when changed using the input) to
return it instead of the guessed country on future `guess` call.
#### Phone Number Formats
A phone number format is a string representing a format, allowing to change the display format of a
phone number in input. Here are the available format (provided
by [awesome-phonenumber](https://www.npmjs.com/package/awesome-phonenumber)):
- `e164`: `+46707123456`
- `international`: `+46 70 712 34 56`
- `national`: `070-712 34 56`
- `rfc3966`: `tel:+46-70-712-34-56`
- `significant`: `707123456`
#### Message options
An object containing the input `label` (or `aria-label` if no label) and an example of a valid phone
number for active country.
```typescript
type MessageOptions = {
label?: Message;
country: Country;
example: string;
}
```
#### Message
A type representing a localized message for the input which will be used as the label, hint, etc.
```typescript
export type Message = string | undefined;
```
#### Message resolver
A type representing a function to resolve a message using current message options or directly the
message.
```typescript
export type MessageResolver = ((options: MessageOptions) => Message) | Message;
```
## Contributing
Please see [CONTRIBUTING file](CONTRIBUTING.md) for more details.
Informal discussion regarding bugs, new features, and implementation of existing features takes
place in the
[GitHub issue page of this repository](https://github.com/paul-thebaud/v-phone-input/issues).
## Credits
- [Paul Thébaud](https://github/paul-thebaud)
- [CoWork'HIT](https://coworkhit.com)
- [All Contributors](https://github.com/paul-thebaud/v-phone-input/graphs/contributors)
Inspired by [vue-tel-input](https://github.com/iamstevendao/vue-tel-input)
and [vue-tel-input-vuetify](https://github.com/yogakurniawan/vue-tel-input-vuetify).
## License
`v-phone-input` is an open-sourced software licensed under the
[MIT license](https://opensource.org/licenses/MIT).