Phone input

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

inputsnew
Preview
Phone input preview
Type + to search by calling codeValid United States number

When to use

  • 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.

When not to use

  • 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.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "phone-input",
  "type": "registry:ui",
  "title": "Phone input",
  "description": "A phone field with a country picker, formatting as you type, and E.164 output.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/phone-input/phone-input.tsx",
      "type": "registry:component",
      "target": "@components/manicat/phone-input/phone-input.tsx"
    },
    {
      "path": "registry/components/phone-input/phone-input.module.css",
      "type": "registry:component",
      "target": "@components/manicat/phone-input/phone-input.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/phone-input",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/phone-input",
    "markdown": "https://uiarc.dev/components/phone-input/markdown",
    "tags": [
      "inputs",
      "new"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { Button } from "@/components/manicat/button/button";
import { PhoneInput } from "@/components/manicat/phone-input/phone-input";

export function PhoneInputExample() {
  return (
    <div className="flex w-full max-w-md items-start gap-3">
      <div className="min-w-0 flex-1">
        <PhoneInput label="Mobile number" defaultCountry="US" defaultValue="+12015550123" description="Type + to search by calling code" />
      </div>
      <Button className="mt-[30px]">Send code</Button>
    </div>
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Contact phone Country picker and number in one field, prefilled from the user's profile.

Contact phone Country picker and number in one field, prefilled from the user's profile. preview
Valid United Kingdom number

Keyboard interactions

KeysAction
+ (in the number)Opens the picker with a calling code search.
ArrowDown / ArrowUp (on the country button)Opens the picker.
Any letter (on the country button)Opens the picker searching for that letter.
ArrowDown / ArrowUp / PageDown / PageUpMoves through countries while searching.
Home / EndFirst or last country when the search is empty.
EnterPicks the highlighted country and returns to the number.
EscapeCloses the picker and returns to the country button.
Backspace / DeleteOver a separator, deletes the digit beside it.

Accessibility

  • 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.

Motion

  • 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.

Responsive behavior

  • 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.

Performance

  • 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

  • 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.