Phone Input

Phone number input with a country dialing-code selector.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/phone-input.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; /** Country option rendered in the dialing-code selector. */ export type PhoneCountry = { code: string; dialCode: string; label: string; }; const defaultCountries: PhoneCountry[] = [ { code: "US", dialCode: "+1", label: "United States" }, { code: "GB", dialCode: "+44", label: "United Kingdom" }, { code: "CA", dialCode: "+1", label: "Canada" }, { code: "AU", dialCode: "+61", label: "Australia" }, { code: "DE", dialCode: "+49", label: "Germany" }, { code: "FR", dialCode: "+33", label: "France" }, { code: "IN", dialCode: "+91", label: "India" }, { code: "JP", dialCode: "+81", label: "Japan" }, ]; /** Phone number input with a leading country dialing-code selector. */ export type PhoneInputProps = Omit< React.ComponentPropsWithoutRef<"input">, "type" > & { countries?: PhoneCountry[]; defaultCountry?: string; onCountryChange?: (code: string) => void; }; const PhoneInput = ({ className, countries = defaultCountries, defaultCountry, onCountryChange, ref, ...props }: PhoneInputProps & { ref?: React.Ref<HTMLInputElement> }) => { const [country, setCountry] = React.useState( defaultCountry ?? countries[0]?.code ?? "", ); return ( <div className={cn( "flex h-10 w-full items-center rounded-md border border-input bg-background text-sm ring-offset-background focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2", className, )} > <select aria-label="Country dialing code" className="h-full rounded-l-md border-0 border-r border-input bg-transparent py-2 pl-3 pr-2 text-sm focus-visible:outline-none disabled:cursor-not-allowed" onChange={(event) => { setCountry(event.target.value); onCountryChange?.(event.target.value); }} value={country} > {countries.map((item) => ( <option key={item.code} value={item.code}> {item.dialCode} </option> ))} </select> <input {...props} className="h-full w-full flex-1 rounded-r-md bg-transparent px-3 py-2 placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed" ref={ref} type="tel" /> </div> ); }; PhoneInput.displayName = "PhoneInput"; export { PhoneInput };

Dependencies

  • @vllnt/ui@^0.2.1