Skip to content

Vue composable

usePhoneInput is a Vue composable you can use to create a custom phone input component. It is used internally by the VPhoneInput Vuetify component. It provides the same behavioral features (automatic formatting, country detection, etc.), but does not provide UI related features (autocomplete, flags, etc.). Every feature which can be used with the composable will have a Composable tag inside this documentation.

Installation

sh
$ npm add v-phone-input
sh
$ pnpm add v-phone-input
sh
$ yarn add v-phone-input
sh
$ bun add v-phone-input

Usage

When using the composable, the first thing to do is to define your reusable phone input component. Inside it, you can use usePhoneInput.

In the following example, we define a PhoneInput component which supports a custom label property and country/phone models. It will render the input through the native HTML select and input elements.

After that, the PhoneInput component is used inside a SignInForm component.

vue
<script
  setup
  lang="ts"
>
import { usePhoneInput } from 'v-phone-input';
import { toRef, useId } from 'vue';

const props = defineProps<{
  label?: string;
}>();

const country = defineModel<string>("country");
const modelValue = defineModel<string>();

const id = useId();

const {
  countryInputRef,
  phoneInputRef,
  countries,
  phoneValid,
  phone,
  countryLabel,
  label,
  example,
  invalidMessage,
} = usePhoneInput({
  label: toRef(props, "label"),
  country,
  modelValue,
});
</script>

<template>
  <div :class="$style.phoneInput">
    <label
      :for="`country-${id}`"
      :class="$style.label"
    >
      {{ countryLabel }}
    </label>
    <select
      ref="countryInputRef"
      v-model="country"
      :id="`country-${id}`"
      :class="$style.input"
    >
      <option
        v-for="country in countries"
        :key="`countries-${country.iso2}`"
        :value="country.iso2"
      >
        {{ country.name }}(+{{ country.dialCode }})
      </option>
    </select>
    <label
      :for="`phone-${id}`"
      :class="$style.label"
    >
      {{ label }}
    </label>
    <input
      ref="phoneInputRef"
      v-model="phone"
      :id="`phone-${id}`"
      :aria-describedby="`phone-${id}-message`"
      type="tel"
      :placeholder="example"
      :class="$style.input"
    >
    <p
      :id="`phone-${id}-message`"
      :class="$style.message"
    >
      {{ phoneValid ? '' : invalidMessage }}
    </p>
  </div>
</template>

<style module>
.phoneInput {
  display: flex;
  flex-direction: column;
}

.label {
  font-size: 0.875rem;
  margin-block-end: 4px;
}

.input {
  font-size: 1rem;
  padding: 6px 12px;
  margin-block-end: 16px;
  border-radius: 8px;
  border: 1px solid #eceff1;
  background: #eceff1;
}

.message {
  font-size: 0.875rem;
  margin-block: 0;
}
</style>
vue
<script
  lang="ts"
  setup
>
import { ref } from 'vue';
import Definition from './Definition.vue';

const phone = ref('');
const password = ref('');

const onSubmit = async () => {
  // ...
};
</script>

<template>
  <form @submit.prevent="onSubmit">
    <div>
      <definition
        v-model="phone"
        label="Phone"
      />
    </div>
    <div>
      <label for="password">
        Password
      </label>
      <input
        id="password"
        v-model="password"
        name="password"
        type="password"
      />
    </div>
  </form>
</template>

INFO

Passing countryInputRef and phoneInputRef is currently only used for on-blur phone formatting.

Configuration

Composable options

To configure usePhoneInput, you can pass an options object. You can explore available options inside compatible features guides. You can also browse VPhoneInputComposableOptions for an exhaustive list where every option is described with its goal, remarks and default value if available.

Most options are using MaybeRef, which means those can be passed as ref or direct values. Passing a ref will make the related behaviors reactive: as an example, passing a reactive displayFormat will make the input value format on every change.

INFO

Only the required modelValue, and optional country, countryInputRef, and phoneInputRef must be refs and cannot be direct values.

vue
<script
  lang="ts"
  setup
>
import { usePhoneInput } from 'v-phone-input';
import { toRef } from 'vue';

const props = defineProps<{
  label?: string;
}>();

const country = defineModel<string>();
const modelValue = defineModel<string>();

const {
  // ...
} = usePhoneInput({
  // Mandatory reactive options.
  country,
  modelValue,
  // Maybe reactive options.
  label: toRef(props, 'label'),
  displayFormat: 'e164',
});
</script>

Providing default options

If you want to define default values for options, you can also use providePhoneInputOptions to a parent of the component using usePhoneInput.

vue
<script
  lang="ts"
  setup
>
import { providePhoneInputOptions } from "v-phone-input";

providePhoneInputOptions({
  displayFormat: "e164",
});
</script>

<template>
  <slot />
</template>
vue
<script
  lang="ts"
  setup
>
import PhoneInputOptionsProvider from './PhoneInputOptionsProvider.vue';
import SignInForm from "./SignInForm.vue";
</script>

<template>
  <phone-input-options-provider>
    Render something deeper which uses phone input...
    <sign-in-form />
  </phone-input-options-provider>
</template>

API reference

You can check out every option and provided value from the composable by reading usePhoneInput.