@input-kit/phone
v0.2.2
Published
Headless phone input with full world country codes, libphonenumber formatting, and TypeScript
Downloads
2,265
Maintainers
Readme
@input-kit/phone
Headless React phone input with a complete world country-code dataset, searchable country selection, and libphonenumber-js powered formatting and validation.
Latest update
0.2.2 — npm README cleanup: release notes stay inline; removed pointers to repo-only markdown files.
0.2.0 — structured validation (ValidationReason, validatePhoneNumber, onValidationChange), parsePhoneValue, getCountryOptions(), improved PhoneInput a11y (click-outside, listbox ARIA) and RTL tests.
Features
- 245 supported calling regions derived from
libphonenumber-jsmetadata andworld-countries - Headless hook via
usePhoneInput()plus an optional unstyled referencePhoneInputcomponent (class names only — no bundled CSS) - Searchable country selector with country name, ISO code, and dial-code matching
- Real formatting and validation powered by
libphonenumber-js - International detection for pasted or typed
+/00numbers - TypeScript-first exports for countries, helpers, hook return values, and component refs
Installation
npm install @input-kit/phoneQuick Start
Component
import { PhoneInput } from '@input-kit/phone';
import '@your-app/phone-input.css'; // style .phone-input-* classes
function Example() {
return (
<PhoneInput
defaultCountry="US"
onChange={(phone, country) => {
console.log(phone, country?.code);
}}
/>
);
}Hook
import { usePhoneInput } from '@input-kit/phone';
function Example() {
const {
inputProps,
country,
countryButtonProps,
filteredCountries,
selectCountry,
isOpen,
isValid,
} = usePhoneInput({
defaultCountry: 'US',
onChange: (phone, nextCountry) => console.log(phone, nextCountry?.dialCode),
});
return (
<div>
<button {...countryButtonProps}>
{country?.flag} {country?.dialCode}
</button>
{isOpen && (
<div>
{filteredCountries.map((candidate) => (
<button key={candidate.code} onClick={() => selectCountry(candidate)}>
{candidate.flag} {candidate.name} {candidate.dialCode}
</button>
))}
</div>
)}
<input {...inputProps} />
{!isValid && <span>Invalid phone number</span>}
</div>
);
}Phone values
| Field | Meaning |
| --- | --- |
| phone | National digits stored by the hook (default) |
| fullPhone | National number plus dial code when includeDialCode is true |
| onChange(phone, country) | Same contract as phone / includeDialCode |
| E.164 for APIs | parsePhoneValue(phone, country).e164 when valid — prefer this over raw concatenation |
Known behavior
Formatting, length checks, and validity follow libphonenumber-js (same family as react-phone-number-input). The package does not implement per-country rules outside that library.
Form integrations
Controlled value / onChange with usePhoneInput or PhoneInput. React Hook Form: wrap with Controller and pass field.value, field.onChange, and field.onBlur. Use onValidationChange to sync isValid / message with form errors. Submit-time checks: parsePhoneValue(phone, country) or validatePhoneNumber.
Styled example
A minimal Vite demo using only the hook lives in examples/react-styled/.
Migration from 0.1.x
Compatibility aliases remain exported. Prefer the newer names in new code:
| Deprecated | Replacement |
| --- | --- |
| setValue | setPhone |
| value (hook) | phone |
| allowedCountries | onlyCountries |
| excludedCountries | excludeCountries |
| autoDetectCountry | autoDetect |
| toggle / open / close | toggleDropdown / openDropdown / closeDropdown |
| countries (hook list) | filteredCountries |
| countrySelectorProps | countryButtonProps |
Validation is unified in 0.2.0: isValid and error come from the same validatePhoneNumber call. Use validationReason or onValidationChange for structured form messages.
Development
Requires Bun (see packageManager in package.json).
bun install
bun run test
bun run typecheck
bun run build
bun run lintManual browser check: test-demo/ (static HTML).
Exports
Components and hooks
PhoneInputusePhoneInput(options)
Country data
countriesgetCountryByCode(code)getCountryByDialCode(dialCode)getCountriesByDialCode(dialCode)getCountryOptions({ locale?, preferredCountries?, excludeCountries?, onlyCountries? })detectCountryFromPhone(phone)
Utilities
cleanPhone,formatPhone,unformatPhone,validatePhone,validatePhoneLengthvalidatePhoneNumber→{ isValid, reason, message, error }parsePhoneValue→{ country, nationalNumber, e164, isValid }addDialCode,removeDialCode,filterCountries,getPlaceholder
Compatibility aliases: stripNonDigits, detectCountry, formatPhoneNumber, parseToE164, getNationalNumber, isPhoneNumberComplete, formatAsYouType, normalizePhoneNumber, phoneNumbersEqual, getCountryDisplayLabel, limitInputLength.
usePhoneInput(options)
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| defaultCountry | string | 'US' | Default selected country |
| preferredCountries | string[] | - | Countries shown first in search results |
| excludeCountries | string[] | - | Countries to exclude |
| onlyCountries | string[] | - | Restrict selection to these countries |
| autoDetect | boolean | true | Detect country from international numbers |
| formatOnType | boolean | true | Apply live formatting |
| includeDialCode | boolean | false | Return values with dial code included |
| required | boolean | false | Empty value is invalid |
| validator | (phone, country) => boolean | - | Custom validation override |
| onValidationChange | (state) => void | - | Fires when validation result changes |
Important returned fields: phone, fullPhone, country, isValid, validationReason, error, onValidationChange, filteredCountries, inputProps, countryButtonProps, dropdownProps, getCountryOptionId.
License
MIT © Input Kit
