PhoneNumberField
A phone number input widget that combines country dialing-code selection, phone number formatting, and validation feedback in one reusable field. It uses @adaptavant/libphonenumber for parsing and as-you-type formatting, and exposes label, placeholder, country search, error, warning, size, and variant props so product surfaces can keep phone entry consistent without rebuilding the same field logic. variant="subtle" renders the inline ghost treatment.
Quick Start
- Installation
npm install @adaptavant/phone-number-field- Import
import { PhoneNumberField } from '@adaptavant/phone-number-field';
Key Features
The PhoneNumberField component provides a complete phone entry experience:
- Country selection — Shows a dialing-code dropdown with searchable country options.
- As-you-type formatting — Formats the phone number as the user enters digits based on the selected country.
- Initial value sync — Accepts
e164,countryCode, andcountryNamethroughphoneNumberPropsso existing contact data can hydrate the field. - Validation feedback — Supports error and warning states through
isErrorField,isWarningField, anderrorMessage. - Localized labels — Lets consumers customize the country dropdown label, search copy, input label, and placeholder.
- Change callbacks — Emits structured phone data when the number changes or the country is selected.
Basic Usage
Pass phoneNumberProps to initialize the selected country and phone number. The e164 value is formatted for display using the supplied country information.
return (
<PhoneNumberField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
e164: '+15034524242',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
},
}}
/>
);
Inline variant
Set variant="subtle" for the inline ghost treatment. Both controls stay borderless at rest, pick up a neutral background on hover, and show a border on focus. size still applies. Field messages stay on the phone input.
return (
<PhoneNumberField
errorMessage="Enter a valid phone number"
phoneNumberProps={{
countryCode: 'AU',
countryName: 'Australia',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
placeholder: 'Phone number',
},
}}
variant="subtle"
/>
);
Empty Field
Omit e164 when the field should start empty. Provide the country details to control which dialing code is selected by default.
return (
<PhoneNumberField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
placeholder: 'Enter your phone number',
},
}}
/>
);
With Custom Labels
Use nested countryCodeProps and inputProps to customize field labels, search text, fallback copy, and the phone input placeholder.
return (
<PhoneNumberField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
countryCodeProps: {
label: 'Dialing code',
searchPlaceholder: 'Find a country',
searchResultFallback: 'No matching country',
},
inputProps: {
label: 'Mobile number',
placeholder: 'Add mobile number',
},
}}
/>
);
With Error Message
Set isErrorField with errorMessage when the field should display an error state. The same errorMessage text is also used for warning states when isWarningField is set.
return (
<PhoneNumberField
errorMessage="Enter a valid phone number"
isErrorField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
e164: '+1503',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
},
}}
/>
);
Large Size
Use size="large" when the phone field needs to align with larger form controls.
return (
<PhoneNumberField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
e164: '+15034524242',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
},
}}
size="large"
/>
);
formatPhoneNumberByLocation — National format
Formats a phone number into national format when the number's country matches the user's geolocation.
return (
<Box className="flex flex-col gap-3">
<Text className="text-body-14">
US number · US geolocation →{' '}
{formatPhoneNumberByLocation('+16469804127', 'US')}
</Text>
<Text className="text-body-14">
UK number · GB geolocation →{' '}
{formatPhoneNumberByLocation('+442071838750', 'GB')}
</Text>
</Box>
);
formatPhoneNumberByLocation — International format
Formats a phone number into international format when the number's country does not match the user's geolocation.
return (
<Box className="flex flex-col gap-3">
<Text className="text-body-14">
US number · GB geolocation →{' '}
{formatPhoneNumberByLocation('+16469804127', 'GB')}
</Text>
<Text className="text-body-14">
UK number · US geolocation →{' '}
{formatPhoneNumberByLocation('+442071838750', 'US')}
</Text>
</Box>
);
formatPhoneNumberByLocation — Edge cases
Empty or invalid inputs are returned unchanged. Typical usage is displaying a stored E.164 number in a UI based on the user's location.
function ContactCard({ phone, userCountry }) {
const displayPhone = formatPhoneNumberByLocation(phone, userCountry);
return <Text className="text-body-14">{displayPhone}</Text>;
}
return (
<Box className="flex flex-col gap-3">
<Text className="text-body-14">
Empty input → "{formatPhoneNumberByLocation('', 'US')}"
</Text>
<Text className="text-body-14">
Invalid input → {formatPhoneNumberByLocation('notvalid', 'US')}
</Text>
<ContactCard phone="+16469804127" userCountry="US" />
</Box>
);
With End Adornment
Use inputProps.adornmentEnd to render trailing content inside the phone number input, such as a copy affordance. The node sits inside the input's focus container, so it shares the field's hover surface instead of being a separate target. Styling, interactivity, and accessibility of the node are the consumer's responsibility.
return (
<PhoneNumberField
phoneNumberProps={{
countryCode: 'US',
countryName: 'United States',
e164: '+15034524242',
countryCodeProps: {
label: 'Country',
},
inputProps: {
label: 'Phone number',
adornmentEnd: (
<IconButton
aria-label="Copy phone number"
className="me-2"
icon={ClipboardCopyIcon}
size="small"
variant="neutralTertiary"
/>
),
},
}}
/>
);
API Reference
PhoneNumberField
Props
| Prop | Default | Description |
|---|---|---|
variant? | 'standard' | 'standard' | 'subtle'standard keeps the outlined country dropdown and phone input. subtle is the inline ghost treatment: no border at rest, a neutral background on hover, and a border on focus. It uses the existing InlineField wrapper. The country column is 100px below the lg breakpoint and 80px from lg up at size="standard", and 100px at size="large". Error and warning messages stay on the phone input. Labels stay available to assistive technology and are visually hidden. |
children? | undefined | React.ReactNodeOptional content rendered before the country selector and phone input row. |
phoneNumberProps? | {} | PhoneNumberPropsSyncInitial phone value and nested props for the country dropdown and phone text input. |
phoneNumberProps.e164? | undefined | stringPhone number in E.164 format. Used to populate and format the visible phone input. |
phoneNumberProps.countryCode? | undefined | stringISO region code for the selected country, for example 'US', 'IN', or 'GB'. |
phoneNumberProps.countryName? | undefined | stringDisplay name of the selected country, used to resolve the dialing code option. |
phoneNumberProps.errorMessage? | undefined | stringError text associated with the phone value. Used when the component syncs validation state from props. |
phoneNumberProps.countryCodeProps? | undefined | CountryCodeDropdownPropsLabels and fallback copy for the country dialing-code dropdown. |
phoneNumberProps.countryCodeProps.label? | undefined | stringLabel shown above the country dialing-code dropdown. |
phoneNumberProps.countryCodeProps.searchPlaceholder? | 'Search countries' | stringLabel text for the country search field inside the dropdown. |
phoneNumberProps.countryCodeProps.searchResultFallback? | 'No matching results' | stringMessage shown when the country search has no matching results. |
phoneNumberProps.inputProps? | undefined | PhoneTextInputPropsLabels and placeholder copy for the phone number text input. |
phoneNumberProps.inputProps.label? | undefined | stringLabel shown above the phone number input. |
phoneNumberProps.inputProps.placeholder? | 'Enter your phone number' | stringPlaceholder shown in the phone number input. |
phoneNumberProps.inputProps.adornmentEnd? | undefined | React.ReactNodeContent rendered at the trailing end of the phone number input, inside its focus container so it shares the field's hover surface. The consumer owns the node's styling, interactivity, and accessibility. |
errorMessage? | '' | stringField message shown when the component is in an error or warning state. |
isErrorField? | false | booleanForces the phone input into an error state and displays errorMessage. |
isWarningField? | false | booleanShows the field message in a warning state. |
onPhoneInputOnChangeProps? | undefined | (event: PhoneInputEvent) => voidCallback fired when the phone number changes or a country is selected. The event includes formatted phone values, region code, validity, and callback type. |
size? | 'standard' | 'standard' | 'large'Sets the EDS field size for the country dropdown and phone input. Applies in both standard and subtle variants. |
className? | '' | stringCustom class applied to the root element. |
classNames? | {} | Partial<Record<'root' | 'fieldRow' | 'phoneInputWrapper', string>>Slot-level class names for the root, field row, and phone input wrapper. |
style? | {} | React.CSSPropertiesInline style applied to the root element. |
styles? | {} | Partial<Record<'root' | 'fieldRow' | 'phoneInputWrapper', React.CSSProperties>>Slot-level inline styles for the root, field row, and phone input wrapper. |
Callback event types
| Type | Data | Description |
|---|---|---|
PHONE_NUMBER_INPUT_CHANGE | PhoneInputChangeCallbackData | Fired when the phone input value changes. |
COUNTRY_SELECT | PhoneInputChangeCallbackData & { dialingCode: string; countryName: string } | Fired when a country option is selected from the dropdown. |
Style API
Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.
Please refer to the Style API documentation for more insights.