mwui
GitHub

Phone Input

An international phone input with country select and formatting.

""

Try pasting +44 7700 900123 โ€” the country follows the number.

Installation

Usage

A searchable country picker joined to a number field, storing one E.164 string. Country lookup and digit grouping ship in the component, so nothing here pulls in a metadata library.

import { PhoneInput } from "@/components/ui/phone-input";

export function ContactField() {
  const [phone, setPhone] = React.useState("");

  return <PhoneInput value={phone} onValueChange={setPhone} defaultCountry="PT" />;
}

The value is "+351912345678" โ€” country code included, no spaces, no punctuation. That is what tel: links, SMS providers, and every phone column want, and it is the one format that survives a user changing country later.

Pasting a full number

Paste +44 7700 900123 into the field and the country switches to the United Kingdom on its own, because the dial code is in the string. Longest-prefix matching means +1 picks the US while +351 picks Portugal rather than a shorter partial match.

Setting value from outside behaves the same way, so a number loaded from your API renders with the right flag without you resolving it first.

Countries

countries replaces the built-in list โ€” restrict it to where you actually operate, reorder it to put your main market first, or extend it:

import { PhoneInput, DEFAULT_COUNTRIES } from "@/components/ui/phone-input";

<PhoneInput countries={DEFAULT_COUNTRIES.filter((country) => EU.includes(country.code))} />;

Each entry is { code, name, dial, groups? }. groups is the digit grouping used for display โ€” [3, 3, 3] renders 912 345 678. It only affects presentation; the stored value never contains spaces.

Flags are emoji derived from the ISO code, so there are no image assets to host and nothing to localize.

Scope

Formatting here is grouping, not validation. Real phone-number rules โ€” valid prefixes per carrier, national trunk digits, variable lengths โ€” need a metadata library such as libphonenumber-js, which is roughly 145 kB. If you need to verify a number, validate on the server; if you need it to look right as it is typed, this is enough.

Props

PropDescription
value / defaultValue / onValueChangeControlled or uncontrolled E.164 string.
defaultCountryISO code selected before the user picks one. Defaults to "US".
onCountryChangeCalled with the country whenever it changes.
countriesReplace the built-in list.
nameEmits a hidden input with the E.164 value for plain HTML forms.
aria-labelLabels the number field. Defaults to "Phone number".

Accessibility

The country trigger announces the current country rather than only showing a flag, since flag emoji read as the country name in some screen readers and as nothing in others. The number field is a real type="tel" input with autocomplete="tel", so browser autofill works.