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>
)
}