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- 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-inputAdds 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:
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.
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.
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.