Phone Input

Cleans pasted phone numbers, picks the country from the dial code and checks the digits against libphonenumber ranges, so an unreachable number isn't saved.

StopsUnreachable phone number saved

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/phone-input.json

The files land in your project. They're yours to edit.

Usage

phone-input-usage.tsx
import { PhoneInput } from "@/components/ui/phone-input"

<PhoneInput
  label="Your mobile"
  defaultValue="+91 +91 098765-43210"
  preferredCountries={["IN", "US", "GB", "AE", "SG"]}
  onNumberChange={(e164) => setSaved(e164)}
/>

What it catches

  • Separators, a leading trunk 0 and doubled country codes in pasted numbers
  • Country picked from the dial code and NANP area code
  • Too few or too many digits for the country
  • Line type that doesn't fit (landline or premium-rate where a mobile is expected)
  • E.164 readback of exactly what will be saved

Props

PhoneInput props
PropTypeDefault
label

Visible label.

string"Phone number"
hint

Helper text shown under the field while it is empty.

string—
value

Controlled text in the number field.

string—
defaultValue

Initial text when uncontrolled; it is tidied on mount like a paste.

string""
onValueChange

Called with the field's text on every change.

(value: string) => void—
onNumberChange

Called with the E.164 number (+919876543210), or `null` while it isn't a valid length, plus its line type and range check.

(e164: string | null, country: C, details: PhoneDetails) => void—
expect

`mobile` warns when the number is a landline, toll-free or premium line (an SMS/OTP field). Default `any`.

"mobile" | "any""any"
country

Controlled country. A typed or pasted +code still reports its country through `onCountryChange`.

C—
defaultCountry

Country selected before anything is typed (uncontrolled). Falls back to the first country offered.

C"IN" as C
onCountryChange

Called when the country changes, from the picker or a detected dial code.

(country: C) => void—
countries

Countries to offer: built-in codes (`["IN", "US"]`) or your own table. Defaults to all 245 built in.

PhoneCountryList<C>—
preferredCountries

Codes pinned to the top of the picker, in this order.

readonly C[]—
locale

BCP 47 locale for country names and their order (e.g. "de"). Defaults to the English names in the data.

string—
countryLabel

Accessible label of the country picker.

string"Country"
savesAsLabel

Text before the read-back of the stored number.

string"Saves as"
messages

Override the clean-up notes and warnings (see `PHONE_MESSAGES`).

Partial<PhoneMessages>—

* required. Also accepts the native props of <input> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible label; country picker has an aria-label
  • Input has aria-invalid on a warning and is described by a polite hint region
  • Flag and dial code visuals are aria-hidden
  • Animations are skipped under reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Format looks valid is not the same as reachable or owned; verify with an OTP
  • Numbering data comes from libphonenumber and needs updates as plans change
  • Country names are English unless you pass locale

What it stops

Unreachable phone number saved

Also known as

+91 · mobile number · otp · indian phone · phone number · country code · libphonenumber · e.164

Field type

Dependencies

Pairs well with