Phone input

A phone field with a country picker, formatting as you type, and E.164 output.

pnpm dlx shadcn@latest add @uiarc/phone-input
Live · keyboard ready

Text message sign-in

We text a 6-digit code when you sign in on a new device.

Type + to search by calling code
Try
  • Sign up, checkout, and contact forms that need a phone number in E.164.
  • International audiences where the country and calling code must be clear.
  • Two factor setup before sending an SMS code.
  • Use input with type="tel" when you only store free text and never dial or text the number.
  • Use otp-input for the verification code itself.
  • Use a full metadata library if you need carrier or number type validation beyond length.

Installation

Add Phone input with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/phone-input

Adds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { useState } from "react";import { PhoneInput } from "@/registry/components/phone-input/phone-input"; export function ContactPhone() {  const [phone, setPhone] = useState("");  const [valid, setValid] = useState(false);  return (    <PhoneInput      label="Phone number"      name="phone"      value={phone}      onValueChange={(value, details) => { setPhone(value); setValid(details.valid); }}      defaultCountry="GB"      preferredCountries={["GB", "IE", "US"]}      description={valid ? undefined : "We only text about your order"}    />  );}

API reference

3 parts. The first is the root.

PhoneInput

A phone number field with a searchable country picker that grows out of the flag button, formatting as you type and returning E.164.

PropTypeDefaultDescription
labelRequiredstring–Field label.
hideLabelbooleanfalseKeeps the label for screen readers only.
valuestring–Controlled number in E.164, such as "+14155550132". An empty string clears the field.
defaultValuestring–Starting number when uncontrolled.
onValueChange(value: string, details: PhoneInputDetails) => void–Fires on every edit with the E.164 number (empty without digits) and { country, formatted, status, valid }.
countrystring–Controlled ISO code of the selected country.
defaultCountrystring"US"ISO code used until someone picks a country or enters an international number.
onCountryChange(iso: string) => void–Called when the country changes, including from a pasted number.
countriesstring[]–Limit the picker to these ISO codes.
preferredCountriesstring[]["US", "CA", "GB"]Pinned at the top of the picker under Suggested.
descriptionstring–Hint under the field.
errorstring–Replaces the built-in validation message.
validatebooleantrueShow a message after blur when the number has the wrong length.
disabledbooleanfalseDisables the number and the picker.
requiredboolean–Marks the number input required.
namestring–Adds a hidden input carrying the E.164 value for native form submission.
idstring–Id of the number input.
classNamestring–Class on the root.
onBlur(event: FocusEvent<HTMLInputElement>) => void–Called when the number input loses focus.
refRef<HTMLInputElement>–Forwarded to the number input.

parsePhoneNumber / formatPhoneNumber / formatNational

Helpers: parse "+44 (0)7911…" or "0044…" into { country, national }, format E.164 for display, or format national digits for a country.

PropTypeDefaultDescription
inputRequiredstring–parsePhoneNumber(input, pool?) returns { country, national } or null.
e164Requiredstring–formatPhoneNumber(e164) returns "+44 7400 123456", or the input when it can not be read.
entry, digitsRequiredPhoneCountry, string–formatNational(entry, digits) formats national digits, keeping a typed trunk prefix.

PHONE_COUNTRIES / flagOf

The built-in table of 49 countries with calling codes, patterns, trunk prefixes, and examples, and a helper that turns an ISO code into a flag emoji.

No props.

+(in the number)
Opens the picker with a calling code search.
ArrowDownorArrowUp (on the country button)
Opens the picker.
Any letter (on the country button)
Opens the picker searching for that letter.
ArrowDownorArrowUporPageDownorPageUp
Moves through countries while searching.
HomeorEnd
First or last country when the search is empty.
Enter
Picks the highlighted country and returns to the number.
Escape
Closes the picker and returns to the country button.
BackspaceorDelete
Over a separator, deletes the digit beside it.
  • The search is a combobox with aria-activedescendant over a listbox of options grouped into Suggested and All countries.
  • The country button reads "Country, <name> +<code>"; picks, pasted country changes, and result counts are announced politely.
  • The number input is type="tel" with autocomplete="tel"; errors are linked with aria-describedby and shown with role="alert" after blur.
  • A valid number is announced as "Valid <country> number" while a check appears.
  • One surface springs its width, height, and corner radius from the country button into the list, and back when closing.
  • The flag and code roll in the direction of the list when the country changes; the highlight glides between rows.
  • Messages open on a height spring with a small rise and blur; the valid check pops in.
  • Reduced motion jumps the surface to size and replaces rolls, blurs, and glides with short fades.
  • The open list is as wide as the field up to 340px, measured with a ResizeObserver, so it fits phones without covering the page.
  • The number input uses inputMode="tel" for the phone keypad; hover styles apply only on fine pointers.
  • The country table is about 50 entries inline, with no network request; search filters it on each keystroke.
  • The morph animates width and height on one element; keep one picker open at a time.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Store the E.164 value; format it for display with formatPhoneNumber. details.valid tells you when the length matches the country.
  • Validation checks length per country only; confirm ownership with an OTP step (otp-input) when it matters.
  • Pasted or autofilled international numbers choose their own country, and +1 splits into Canada by area code.
  • Restrict countries with countries and pin the likely ones with preferredCountries; the table ships inline with no metadata download.

The full library index for assistants is at /llms.txt.