GitHub

Phone Input

A phone number input with country selection, formatting, and validation.

phone-input-demo.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputDemo() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				placeholder="Enter a phone number"
			/>
		</div>
	)
}

About

This component was inspired by the Phone Input component created by @omeralpi_.

Installation

npx shadcn@latest add @9ui/phone-input

Usage

Imports
import { PhoneInput } from "@/components/ui/phone-input"
Anatomy
<PhoneInput />

Examples

Default Country

phone-input-default-country.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputDefaultCountry() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				defaultCountry="TR"
				placeholder="Enter a phone number"
			/>
		</div>
	)
}

Internationalization Format

phone-input-internationalization.tsx
"use client"
 
import { useState } from "react"
import tr from "react-phone-number-input/locale/tr"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputInternationalization() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				labels={tr}
				defaultCountry="TR"
				placeholder="Telefon numarası"
			/>
		</div>
	)
}

Force International Format

phone-input-international.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputInternational() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				international
				defaultCountry="TR"
				placeholder="Enter a phone number"
			/>
		</div>
	)
}

Force National Format

phone-input-national.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputNational() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				international={false}
				defaultCountry="TR"
				placeholder="Enter a phone number"
			/>
		</div>
	)
}

Initial Value Format

phone-input-initial-value-format.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputInitialValueFormat() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
 
	return (
		<div className="w-[300px]">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				initialValueFormat="national"
				placeholder="Enter a phone number"
			/>
		</div>
	)
}

Formatting Value

phone-input-formatting-value.tsx
"use client"
 
import { useState } from "react"
import {
	Country,
	formatPhoneNumber,
	formatPhoneNumberIntl,
	getCountryCallingCode,
} from "react-phone-number-input"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputFormattingValue() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
	const [country, setCountry] = useState<Country>()
 
	return (
		<div className="w-[300px] space-y-4">
			<PhoneInput
				value={phoneNumber}
				onChange={setPhoneNumber}
				onCountryChange={setCountry}
				placeholder="Enter a phone number"
			/>
			<div className="space-y-2 text-sm">
				<div>
					<span className="font-semibold">National:</span>{" "}
					{phoneNumber && formatPhoneNumber(phoneNumber)}
				</div>
				<div>
					<span className="font-semibold">International:</span>{" "}
					{phoneNumber && formatPhoneNumberIntl(phoneNumber)}
				</div>
				<div>
					<span className="font-semibold">Country code:</span>{" "}
					{country && getCountryCallingCode(country)}
				</div>
			</div>
		</div>
	)
}

With Validation

phone-input-with-validation.tsx
"use client"
 
import { useState } from "react"
import { isValidPhoneNumber } from "react-phone-number-input"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputWithValidation() {
	const [phoneNumber, setPhoneNumber] = useState<string>()
	const [error, setError] = useState<string>()
 
	const handleChange = (value: string | undefined) => {
		setPhoneNumber(value)
		if (value && !isValidPhoneNumber(value)) {
			setError("Please enter a valid phone number")
		} else {
			setError(undefined)
		}
	}
 
	return (
		<div className="w-[300px] space-y-2">
			<PhoneInput
				value={phoneNumber}
				onChange={handleChange}
				placeholder="Enter a phone number"
				aria-invalid={!!error}
			/>
			{error && <p className="text-destructive text-sm">{error}</p>}
			{phoneNumber && !error && (
				<p className="text-muted-foreground text-sm">
					Valid phone number entered
				</p>
			)}
		</div>
	)
}

Form

phone-input-form.tsx
"use client"
 
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { isValidPhoneNumber } from "react-phone-number-input"
import { z } from "zod"
 
import { Button } from "@/components/ui/button"
import {
	Form,
	FormControl,
	FormField,
	FormItem,
	FormLabel,
	FormMessage,
} from "@/components/ui/form"
import { PhoneInput } from "@/components/ui/phone-input"
 
const schema = z.object({
	phoneNumber: z.string().refine(isValidPhoneNumber, "Invalid phone number"),
})
 
type FormValues = z.infer<typeof schema>
 
export function PhoneInputForm() {
	const form = useForm<FormValues>({
		resolver: zodResolver(schema),
		defaultValues: {
			phoneNumber: "",
		},
	})
 
	const onSubmit = (data: FormValues) => {
		console.log(data)
	}
 
	return (
		<Form {...form}>
			<form
				onSubmit={form.handleSubmit(onSubmit)}
				className="flex w-[300px] flex-col gap-4"
			>
				<FormField
					name="phoneNumber"
					control={form.control}
					render={({ field, fieldState }) => (
						<FormItem>
							<FormLabel>Phone Number</FormLabel>
							<FormControl>
								<PhoneInput
									{...field}
									placeholder="Enter a phone number"
									aria-invalid={!!fieldState.error}
								/>
							</FormControl>
							<FormMessage />
						</FormItem>
					)}
				/>
				<Button type="submit">Submit</Button>
			</form>
		</Form>
	)
}