sulaf
DocsComponentsComposables
X
Sections
  • Get Started
  • Installation
  • Components
  • Composables
  • Animations
    Soon
Components
  • Autocomplete
    New
  • Show More
    New
  • Meter
    New
  • Contribution Heatmap
    New
  • Phone Input
    New
  • Typography
    New
  • Code Block
    Soon
  • Code Snippet
    Soon
  • Guided Tour
    Soon
  • Star Rating
    Soon
Composables
  • useGithubProfile
    Beta
  • useIsMac
    New
Animations
Soon

Phone Input

PreviousNext

A comprehensive phone number input component with country selection, formatting, and validation, built on top of libphonenumber-js.

<script setup lang="ts">
import { ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  type CountryCode,
} from '@/components/ui/phone-input'

const phone = ref('')
const country = ref<CountryCode>('US')
</script>

<template>
  <div class="w-full max-w-sm">
    <PhoneInput v-model="phone" v-model:country="country">
      <PhoneInputCountrySelect />
      <PhoneInputField placeholder="Phone number" />
      <PhoneInputClear />
    </PhoneInput>
  </div>
</template>

The PhoneInput component provides a robust solution for capturing and validating international phone numbers, complete with an auto-detecting country dropdown, dynamic formatting, and localized country names.

Installation

pnpm dlx sulaf@latest add phone-input

Usage

<script setup lang="ts">
import { ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  type CountryCode
} from '@/components/ui/phone-input'

const phone = ref('')
const country = ref<CountryCode>('US')
</script>

<template>
  <PhoneInput
    v-model="phone"
    v-model:country="country"
  >
    <PhoneInputCountrySelect />
    <PhoneInputField placeholder="Phone number" />
    <PhoneInputClear />
  </PhoneInput>
</template>

Examples

Variants

The component supports visual variants to indicate success, warning, or danger states out of the box.

<script setup lang="ts">
import { ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  type CountryCode,
} from '@/components/ui/phone-input'
import { Label } from '~/components/ui/label'

const phone1 = ref('+1 415 555 2671')
const country1 = ref<CountryCode>('US')

const phone2 = ref('+1 415 555 2671')
const country2 = ref<CountryCode>('US')

const phone3 = ref('+1 415 555 2671')
const country3 = ref<CountryCode>('US')

const phone4 = ref('+1 415 555 2671')
const country4 = ref<CountryCode>('US')
</script>

<template>
  <div class="flex w-full max-w-sm flex-col gap-6">
    <div class="flex flex-col gap-2">
      <Label>Default</Label>
      <PhoneInput v-model="phone1" v-model:country="country1" variant="default">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>

    <div class="flex flex-col gap-2">
      <Label>Success</Label>
      <PhoneInput v-model="phone2" v-model:country="country2" variant="success">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>

    <div class="flex flex-col gap-2">
      <Label>Warning</Label>
      <PhoneInput v-model="phone3" v-model:country="country3" variant="warning">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>

    <div class="flex flex-col gap-2">
      <Label>Danger</Label>
      <PhoneInput v-model="phone4" v-model:country="country4" variant="danger">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>
  </div>
</template>

Validation State

You can use the exposed validation utilities to dynamically calculate and style the input based on whether the provided number is valid for the selected country.

Current state: empty
<script setup lang="ts">
import { computed, ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  validatePhoneNumber,
  type CountryCode,
} from '@/components/ui/phone-input'
import { Label } from '~/components/ui/label'

const phone = ref('')
const country = ref<CountryCode>('US')

const validation = computed(() => {
  if (!phone.value) return 'empty'
  const result = validatePhoneNumber(phone.value, country.value)
  return result.success ? 'valid' : result.error
})

const variant = computed(() => {
  if (validation.value === 'empty') return 'default'
  if (validation.value === 'valid') return 'success'
  return 'danger'
})
</script>

<template>
  <div class="flex w-full max-w-sm flex-col gap-4">
    <div class="flex flex-col gap-2">
      <Label>Validation State</Label>
      <PhoneInput v-model="phone" v-model:country="country" :variant="variant">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Enter a valid phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>
    <div class="text-sm text-muted-foreground">
      Current state:
      <span class="font-medium text-foreground">{{ validation }}</span>
    </div>
  </div>
</template>

Phone Format

Control the formatting of the phone number as it's entered. Supports international (default), national, and e164.

<script setup lang="ts">
import { ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  type CountryCode,
  type PhoneFormat,
} from '@/components/ui/phone-input'
import { Label } from '~/components/ui/label'
import { Button } from '~/components/ui/button'

const phone = ref('+1 415 555 2671')
const country = ref<CountryCode>('US')
const format = ref<PhoneFormat>('international')
</script>

<template>
  <div class="flex w-full max-w-sm flex-col gap-4">
    <div class="flex flex-col gap-2">
      <Label
        >Format: <span class="capitalize">{{ format }}</span></Label
      >
      <PhoneInput v-model="phone" v-model:country="country" :format="format">
        <PhoneInputCountrySelect />
        <PhoneInputField placeholder="Phone number" />
        <PhoneInputClear />
      </PhoneInput>
    </div>

    <div class="flex gap-2">
      <Button
        size="sm"
        :variant="format === 'international' ? 'default' : 'outline'"
        @click="format = 'international'"
      >
        International
      </Button>
      <Button
        size="sm"
        :variant="format === 'national' ? 'default' : 'outline'"
        @click="format = 'national'"
      >
        National
      </Button>
      <Button
        size="sm"
        :variant="format === 'e164' ? 'default' : 'outline'"
        @click="format = 'e164'"
      >
        E164
      </Button>
    </div>
  </div>
</template>

Disabled

Display the phone input in a disabled state.

<script setup lang="ts">
import { ref } from 'vue'
import {
  PhoneInput,
  PhoneInputClear,
  PhoneInputCountrySelect,
  PhoneInputField,
  type CountryCode,
} from '@/components/ui/phone-input'
import { Label } from '~/components/ui/label'

const phone = ref('+1 415 555 2671')
const country = ref<CountryCode>('US')
</script>

<template>
  <div class="flex w-full max-w-sm flex-col gap-2">
    <Label>Disabled</Label>
    <PhoneInput v-model="phone" v-model:country="country" disabled>
      <PhoneInputCountrySelect />
      <PhoneInputField placeholder="Phone number" />
      <PhoneInputClear />
    </PhoneInput>
  </div>
</template>

API Reference

PhoneInput (Root)

PropTypeDefaultDescription
v-modelstring''The formatted phone number value.
v-model:countryCountryCode'YE' (or defaultCountry)The currently selected country code.
defaultCountryCountryCode'YE'The default country code when no country is selected.
countriesCountryOption[](Generated)A customized list of countries to display in the dropdown.
disabledbooleanfalseDisables the phone input and dropdown.
variant'default' | 'success' | 'warning' | 'danger''default'Visual variant for the input.
placeholderstring'Phone number'Placeholder text for the input field.
format'international' | 'national' | 'e164''national'Preferred format for the generated phone number string.
localestring(Browser language)The locale used for translating country names.
namestring'phone'Name attribute for the hidden input (for forms).
requiredbooleanfalseMarks the input as required.
autocompletestring'tel'Autocomplete attribute for the input.
autoDetectCountrybooleantrueAutomatically switch the country when the typed number reveals a different country code.

Emits

  • @update:modelValue(payload: string | undefined)
  • @update:country(payload: CountryCode)
  • @onCountryChange(payload: CountryCode)
  • @onClear()

PhoneInputCountrySelect

PropTypeDefaultDescription
showCountryNamebooleanfalseDisplay the country name in the dropdown items alongside the flag and calling code.

Utility Functions

The package exposes several utility functions from the validation.ts and utils.ts modules for working with phone numbers and countries programmatically.

FunctionSignatureDescription
validatePhoneNumber(phone: string, country?: CountryCode) => PhoneValidationResultValidates a phone number and returns a detailed result (success, or specific error like TOO_SHORT, INVALID_COUNTRY).
getPhoneValidationState(phone: string, country?: CountryCode) => PhoneValidationStateReturns a simplified string state: 'empty', 'valid', or a validation error.
isValidPhoneNumber(phone: string) => booleanQuick boolean check if a phone number is valid globally (usually requires international format with +).
isValidPhoneNumberForCountry(phone: string, country: string) => booleanQuick boolean check if a phone number is valid for a specific country.
parsePhone(value: string, countryCode: CountryCode) => PhoneNumber | nullSafely parses a string into a PhoneNumber object from libphonenumber-js.
buildCountryOptions(locale?: string) => CountryOption[]Generates a localized array of countries including their name, flag, and calling code.
Contribution HeatmapTypography

On This Page

InstallationUsageExamplesVariantsValidation StatePhone FormatDisabledAPI ReferencePhoneInput (Root)PhoneInputCountrySelectUtility Functions
© 2026 - Built with shadcn-vue . The source code is available on GitHub.