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-demo.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputDefaultCountryDemo() {
	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-demo.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 PhoneInputInternationalizationDemo() {
	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-demo.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputInternationalDemo() {
	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-demo.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputNationalDemo() {
	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-demo.tsx
"use client"
 
import { useState } from "react"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputInitialValueFormatDemo() {
	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

National: Enter a phone number
International: Enter a phone number
Country code:
phone-input-formatting-value-demo.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 PhoneInputFormattingValueDemo() {
	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)
					) : (
						<span className="text-muted-foreground">Enter a phone number</span>
					)}
				</div>
				<div>
					<span className="font-semibold">International:</span>{" "}
					{phoneNumber ? (
						formatPhoneNumberIntl(phoneNumber)
					) : (
						<span className="text-muted-foreground">Enter a phone number</span>
					)}
				</div>
				<div>
					<span className="font-semibold">Country code:</span>{" "}
					{country && getCountryCallingCode(country)}
				</div>
			</div>
		</div>
	)
}

With Validation

phone-input-with-validation-demo.tsx
"use client"
 
import { useState } from "react"
import { isValidPhoneNumber } from "react-phone-number-input"
 
import { PhoneInput } from "@/components/ui/phone-input"
 
export function PhoneInputWithValidationDemo() {
	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-demo.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 PhoneInputFormDemo() {
	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>
	)
}