# React Phone Input With Country Code and E.164 Value

> A React phone input that formats per country as you type, picks the country from a pasted +code, validates with libphonenumber-js and returns one E.164 value.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 13, 2026
- Category: [Component guides](https://wingo-ui.com/blog/category/components)
- Reading time: 8 min
- Canonical: https://wingo-ui.com/blog/react-phone-input

## TL;DR

A React phone input should let people type the number the way their country writes it, pick the country from a searchable list or from a pasted +code, and hand your code one E.164 string such as +12015550123 instead of formatted text. Validate on the client for feedback and again on the server with libphonenumber-js, using its max metadata when you need strict digit checks. The Wingo UI Phone Input does the formatting, the country detection, the E.164 value and a bottom sheet country list on phones, built on libphonenumber-js.

A react phone input looks like one text field, and it is really four problems: picking a country out of more than 200, formatting the digits the way that country writes them, catching numbers that cannot exist, and getting one clean value into your database. Most forms solve one of these and leave the rest to a regex, which is how you end up with `(201) 555-0123`, `2015550123` and `+1 201 555 0123` for the same customer. This guide covers what a good phone field does, how validation actually works with libphonenumber-js, and how to use the Wingo UI [Phone Input](https://wingo-ui.com/components/phone-input) in a real form.

## What should a React phone input do?

Six behaviors separate a phone field people do not notice from one they fight with:

1. **Format as you type, for the selected country.** A US number reads `201 555 0123` as you type it, a UK mobile reads `7400 123456`, and the caret stays after the digit you just typed instead of jumping to the end.
2. **Detect the country from a pasted or autofilled number.** Typing or pasting `+44 7400 123456` or `0044 7400…` should switch the flag to the United Kingdom by itself.
3. **Drop the trunk prefix.** People type the number the way they dial it at home, often with a leading `0` (or a `1` in the US). The field should keep only the national number.
4. **A searchable country list.** By localized name, English name, ISO code and calling code, with the countries your users pick most at the top.
5. **One value out.** Your form gets `+12015550123`, never the formatted text.
6. **The right keypad on phones.** `inputMode="tel"` and an autocomplete hint so the browser offers the saved number.

## Which react phone input libraries are there?

As of October 2026, from the npm registry and each project's README:

| Option | Latest release | Built on | Styling |
|---|---|---|---|
| [react-phone-input-2](https://github.com/bl00mber/react-phone-input-2) | 2.15.1, Jul 2022 | own country data | `style.css` |
| [react-phone-number-input](https://www.npmjs.com/package/react-phone-number-input) | 3.4.18, Aug 2026 | libphonenumber-js | `style.css` |
| [react-international-phone](https://www.npmjs.com/package/react-international-phone) | 4.8.0, Feb 2026 | own country data | `style.css` |
| [shadcn-phone-input](https://github.com/omeralpi/shadcn-phone-input) | copied source | react-phone-number-input | Tailwind |
| Wingo UI Phone Input | copied source | libphonenumber-js | Tailwind v4 |

All of these are MIT licensed except the Wingo UI component, which is part of the paid Pro plan. Each npm package asks you to import its own stylesheet, and shadcn-phone-input also needs cmdk 1.0.0 per its README. If you want a free npm package, react-phone-number-input is by the author of libphonenumber-js and had a release in August 2026. If you are looking for a react-phone-input-2 alternative because of its age, any of the newer rows works. A shadcn phone input is not in the official shadcn/ui registry (63 items as of October 2026), so shadcn projects use a community wrapper like shadcn-phone-input.

## How do you add a react phone input with country code?

Install it with `npx wingo-ui@latest add phone-input`, then render it with a default country and a few preferred countries for the top of the list:

```tsx
"use client";

import { useState } from "react";
import { PhoneInput, type PhoneDetails } from "@/components/ui/phone-input";

export function ContactPhone() {
  const [phone, setPhone] = useState("");
  const [details, setDetails] = useState<PhoneDetails | null>(null);

  return (
    <div className="flex flex-col gap-2">
      <PhoneInput
        name="phone"
        label="Phone"
        description="We only call about your order."
        defaultCountry="US"
        preferredCountries={["US", "CA", "GB"]}
        locale="en-US"
        value={phone}
        onValueChange={(e164, info) => {
          setPhone(e164);
          setDetails(info);
        }}
        showValidity
      />
      {details?.valid && (
        <p className="text-sm text-muted-foreground">We will call {details.international}.</p>
      )}
    </div>
  );
}
```

Try it below: type a number, then paste one that starts with `+44` and watch the flag change.

> Live demo (Phone Input): Type 2015550123 and watch it format as a US number, then paste +44 7400 123456: the country switches to the United Kingdom and the green check appears once the number is valid. Try it at [Phone Input](https://wingo-ui.com/components/phone-input) and install it with `npx wingo-ui@latest add phone-input`.

The props that matter most:

- **`defaultCountry`** sets the starting country with an ISO code. The component's own default is `"RO"`, so set it for your market.
- **`preferredCountries`** shows those countries first under a Suggested heading. Its default is `["RO", "MD"]`; pass an empty array to drop the headings.
- **`countries`** limits the list, and the countries a typed calling code may pick. All 245 are available by default, in a virtualized list.
- **`countrySelect={false}`** fixes the country: no picker, the calling code shows as a text prefix, and a pasted `+44…` still fills in the national digits.
- **`locale`** sets the language of the country names through `Intl.DisplayNames`. English names stay searchable in every locale.

## What value does the phone input return?

`onValueChange(e164, details)` fires on every edit. `e164` is the number in E.164 format, such as `+12015550123`, as soon as the number is possible for the country, and an empty string before that. `details` is `{ country, national, international, valid, possible }`, so you can tell an empty field from an incomplete one and show the formatted versions without formatting them yourself.

With `name`, the component also renders a hidden input with the E.164 value, so a plain form or a Server Action gets the clean number:

```tsx
import { PhoneInput } from "@/components/ui/phone-input";
import { Button } from "@/components/ui/button";
import { isValidPhoneNumber } from "libphonenumber-js/max";

async function requestCallback(formData: FormData) {
  "use server";
  const phone = String(formData.get("phone") ?? "");
  // max metadata: checks the digits, not only the length
  if (!isValidPhoneNumber(phone)) return;
  console.log("call back", phone);
}

export function CallbackForm() {
  return (
    <form action={requestCallback} className="flex flex-col gap-4">
      <PhoneInput name="phone" label="Phone" defaultCountry="US" preferredCountries={["US", "CA"]} required />
      <Button type="submit">Request a call</Button>
    </form>
  );
}
```

The visible input holds formatted national digits, which is why you should never read it directly. Read the hidden input, or the `e164` argument.

## How does react phone input validation work?

Through libphonenumber-js, which answers two different questions:

- **`isPossible()`**: is the length right for this country?
- **`isValid()`**: do the digits match a real number range for this country?

There is a catch in its README: the default `min` metadata, about 80 kB, does not include the precise patterns for digit validation, so `isValid()` behaves like `isPossible()` with it. The `max` metadata adds about 65 kB and validates the digits. The Phone Input ships with `min` to stay small in the browser, which is plenty for feedback while typing. On the server, where size does not matter, import from `libphonenumber-js/max` as in the Server Action above.

For forms built with react-hook-form, the free [Field](https://wingo-ui.com/components/field) anatomy gives the label, description and error, and the [form validation helpers](https://wingo-ui.com/components/form-validation), also a Pro item, give you a zod rule with the error message already written. Because the visible input holds formatted text, connect the Phone Input through a `Controller`:

```tsx
"use client";

import { useState } from "react";
import { z } from "zod";
import { Controller } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { PhoneInput } from "@/components/ui/phone-input";
import { createValidators, fieldError, useZodForm } from "@/lib/form-validation";

const v = createValidators();
const schema = z.object({ phone: v.phone({ country: "US" }) });

export function PhoneForm({ onSave }: { onSave: (values: z.output<typeof schema>) => Promise<void> }) {
  const form = useZodForm(schema, { defaultValues: { phone: "" } });
  // the typed digits, so an incomplete number gets the phone message instead of "required"
  const [national, setNational] = useState("");
  const error = fieldError(form, "phone");

  return (
    <form onSubmit={form.handleSubmit(onSave)} noValidate className="flex flex-col gap-4">
      <Controller
        control={form.control}
        name="phone"
        render={({ field }) => (
          <PhoneInput
            label="Phone"
            defaultCountry="US"
            value={field.value}
            onValueChange={(e164, details) => {
              setNational(details.national);
              field.onChange(e164);
            }}
            onBlur={field.onBlur}
            ref={field.ref}
            error={error && national ? "Enter a valid phone number" : error}
            required
          />
        )}
      />
      <Button type="submit" loading={form.formState.isSubmitting}>
        Save
      </Button>
    </form>
  );
}
```

`v.phone()` checks the value with `isValidPhoneNumber` from the default `min` metadata, so in the browser it is a length check, and `useZodForm` shows errors after the field is left, then updates them as you type. The `national` state handles one trap: while the number is too short to be possible, `onValueChange` sends an empty string, so zod reports "This field is required" for a field the user has clearly filled in. Keeping `details.national` lets you show "Enter a valid phone number" instead whenever something was typed.

## How does the phone input work on mobile and with a keyboard?

Below 768px the country list opens as a bottom sheet with a search field and 48px rows instead of a popover, because a 245-row list anchored to a small flag button is hard to scroll with a thumb. `responsive={false}` keeps the popover; our [React bottom sheet guide](https://wingo-ui.com/blog/react-bottom-sheet-for-the-web) covers how those sheets drag and snap. The number field uses `inputMode="tel"` for the phone keypad, `autoComplete="tel-national"` by default, and `enterKeyHint="next"`; set it to `"done"` on the last field of a form. Text is 16px on phones so iOS Safari never zooms into the field, and the flag button keeps a 44px hit area.

With a keyboard, Tab reaches the flag button and then the number. On the flag button, Enter, Space or ArrowDown open the list, and a letter jumps to the next country starting with it without opening. In the list the search is focused, arrows, Home, End, PageUp and PageDown move, Enter picks and puts focus back in the number, and Escape closes and returns to the flag button. The flag button's accessible name includes the country and code, such as "Country: United States +1".

If the number is there to receive a sign-in or verification code, the next screen is a code field; our [React OTP input guide](https://wingo-ui.com/blog/react-otp-input) covers paste and SMS autofill for it.

## Which phone input pitfalls show up in production?

- **Storing formatted text.** `(201) 555-0123` loses the country and breaks lookups. Store E.164 and format for display with `details.national` or `details.international`.
- **Trusting the client.** A request can skip your form. Validate the E.164 value again on the server.
- **Windows flags.** Windows does not render flag emoji, so the Phone Input shows the ISO code in a small chip there after mount. Pass `renderFlag` to use your own SVG flags everywhere.
- **Masks that fight paste.** A fixed `(999) 999-9999` mask rejects a pasted `+1 201…`. Format per country from the digits instead, and detect the calling code. For fixed formats that really are fixed, such as a postal code, a masked input is the better tool.

More single-component deep dives live in [component guides](https://wingo-ui.com/blog/category/components), and our guide to [React form components](https://wingo-ui.com/blog/react-form-components-guide) shows how the phone field fits with email, address and payment fields.

## How do you install it?

Run `npx wingo-ui@latest add phone-input`. It copies the Phone Input with the Select and Field parts it uses and installs `libphonenumber-js`, `motion` and `lucide-react`. The Phone Input is part of [Wingo UI Pro](https://wingo-ui.com/pricing), so sign in first with `npx wingo-ui@latest login`; the Field anatomy it renders is free on its own.

## Components in this post

- [Phone Input](https://wingo-ui.com/components/phone-input) (Pro): A React phone number input with a searchable country picker, formatting as you type, validation and one clean E.164 value out. Install: `npx wingo-ui@latest add phone-input`
- [Field](https://wingo-ui.com/components/field) (Free): A React form field wrapper every control shares: label, hint, animated error and counter, fieldsets, and the box recipe all inputs use. Install: `npx wingo-ui@latest add field`
- [Form validation](https://wingo-ui.com/components/form-validation) (Pro): Zod validation rules for React forms with one message set (English or Romanian), react-hook-form glue and a pretend request. Install: `npx wingo-ui@latest add form-validation`

## FAQ

### How do I make a React phone input with a country code?

Put a country button with the flag and calling code in front of the number field, format the national digits for that country as the user types, and switch the country when someone types or pastes a number that starts with + or 00. Store the result as one E.164 string such as +12015550123.

### What is a good react-phone-input-2 alternative?

react-phone-input-2's last npm release, 2.15.1, is from July 2022. Actively released options include react-phone-number-input and react-international-phone on npm, shadcn-style wrappers built on react-phone-number-input, and the Wingo UI Phone Input, which is copied into your project as source and styled with Tailwind CSS.

### How do I validate a phone number in React?

Use libphonenumber-js: isPossible() checks the length for the country and isValid() checks the digits. Its default min metadata only validates length, so on the server import from libphonenumber-js/max when you need strict digit validation, and always validate the E.164 value again there.

### Should I store phone numbers in E.164 format?

Yes. E.164 is the international format with a plus sign, the country calling code and the national number with no spaces, such as +442071838750. It is unambiguous, sorts and compares as plain text, and is what SMS and calling APIs expect.

### Does shadcn/ui have a phone input?

Not in its official registry as of October 2026. Community components such as shadcn-phone-input wrap react-phone-number-input in shadcn styling.

---

Source: https://wingo-ui.com/blog/react-phone-input
