import { Label, PhoneField, PhoneFieldInput } from '@vega-ui/react';
<div>
<Label htmlFor='preview-phone'>Phone</Label>
<PhoneField code='US'>
<PhoneFieldInput id='preview-phone' placeholder='+1 (___) ___-____' />
</PhoneField>
</div>- Browser support
- Newly available 2025
- Chrome 116
- Edge 116
- Firefox 125
- Safari 17
- Safari on iOS 18.3
- Bundle size
Summary
PhoneField is a compound component that combines a masked phone input with a nested country-code Select. It is the consumer-facing phone-entry layer, not just a loose Select plus TextField pairing.
Imports
import {
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectValue,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
type PhoneFieldProps,
} from '@vega-ui/react';Exported Types
PhoneFieldPropsPhoneFieldInputPropsPhoneFieldSelectProps
Minimal Composition
import {
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,
} from '@vega-ui/react';Required Parts
PhoneField: root field state and formatting contextPhoneFieldInput: actual phone number input
Optional Parts
PhoneFieldSelect*: nested country-code picker family built on the same select subsystem contractPhoneFieldSelectHiddenSelect: native form participation for country code
Composition Order
Typical composition:
PhoneFieldPhoneFieldSelectPhoneFieldSelectHiddenSelectPhoneFieldSelectComboboxPhoneFieldSelectListboxPhoneFieldSelectOptionPhoneFieldInput
Variants
- Input-only usage versus full country-select composition
strictMaskversus lenient input- Controlled input value versus uncontrolled form usage
Edge Cases
- Country changes can require reformatting and revalidation.
- Strict masking and paste behavior should both be tested.
- Persist canonical values at the feature boundary, not only the displayed formatted string.
- Autofill, pasted values, and partially typed international prefixes should be tested together, not as isolated cases.
Common Mistakes
- Treating only the visible formatted string as the source of truth.
- Ignoring country changes in validation logic.
- Testing typing flow only and skipping paste or autofill.
Examples
Basic
Basic: simple ISO-based phone mask
code alone sets the format: the mask keeps the US calling code and groups the digits as +1 201 555-0123. A visible Label names the input and autoComplete='tel' lets the browser fill it in.
import { Label, PhoneField, PhoneFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function SimpleIsoBasedPhoneMask() {
const id = useId();
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 280 }}>
<Label htmlFor={id}>Mobile number</Label>
<PhoneField code='US'>
<PhoneFieldInput id={id} name='phone' autoComplete='tel' placeholder='+1 201 555-0123' />
</PhoneField>Basic: country select with flag icons
Flags from @vega-ui/icons sit in each option next to the name and the calling code. The country is controlled, so the trigger shows only the selected flag and its aria-label carries the country name.
import {
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,
} from '@vega-ui/react';Controlled/Stateful
Controlled/Stateful: controlled value with clear action
The parent owns the formatted text, derives the stored digits from it and clears it with an empty string. The readout tells the user when the number is still incomplete.
import { Button, Label, PhoneField, PhoneFieldInput, Text } from '@vega-ui/react';
import { useId, useState } from 'react';
export default function ControlledValueWithClearAction() {
const id = useId();
const [phone, setPhone] = useState('+1 415 555-0132');
const digits = phone.replace(/\D/g, '');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}>
<Label htmlFor={id}>Phone for delivery updates</Label>
<div style={{ display: 'flex', gap: 'var(--spacing-4)' }}>Form/Integration
Form/Integration: uncontrolled native form submit
The input submits the formatted number under name='phone', and PhoneFieldSelectHiddenSelect submits the country under name='country'. required is native validation on the real input.
import {
Button,
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,Layout/Overlay
Layout/Overlay: full field inside a dialog form
The country list opens in the top layer above the modal dialog. The form reads both values with FormData and closes the dialog on submit.
import type { CSSProperties, FormEvent } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Label,
PhoneField,Error
Error: invalid number state
The number is checked on blur and on submit. An incomplete number sets error on the field and aria-invalid on the input and links the message with aria-describedby; typing the last digit clears it.
import { Button, HelperText, Label, PhoneField, PhoneFieldInput, Text } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
const countDigits = (phone: string) => phone.replace(/\D/g, '').length;
export default function InvalidNumberState() {
const id = useId();
const errorId = `${id}-error`;
const [phone, setPhone] = useState('');
const [invalid, setInvalid] = useState(false);
const [saved, setSaved] = useState(false);
Disabled
Disabled: inherited from a fieldset
The input and the country trigger are native controls, so a disabled Fieldset disables both and leaves them out of the form. The switch toggles that state.
import {
Fieldset,
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,Edge
Edge: non-strict mask for lenient paste behavior
With strictMask={false} a pasted number keeps its own calling code, and the field switches the country to match it; onChangeCode reports the detected country. With the default strict mask the same paste would be forced into the current country's code.
import {
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,
Text,
} from '@vega-ui/react';Edge: emoji flags
Options and the value take any content, so emoji flags work without image assets. The trigger reads the selected code from useSelectContext and shows only its flag, as an image named after the country.
import {
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,
useSelectContext,Anatomy
Overview
PhoneField coordinates input masking, country state, and nested select behavior across the root field and the PhoneFieldSelect* family.
Required Parts
PhoneField
Root provider for phone formatting and country state.
PhoneFieldInput
Visible text input for the number value.
Optional Parts
PhoneFieldSelect
Nested country-code picker root.
PhoneFieldSelectHiddenSelect
Native form participation for the country value.
PhoneFieldSelectCombobox
Visible trigger for the country picker.
PhoneFieldSelectValue
Current selected country value or custom render content.
PhoneFieldSelectIcon
Visual affordance for the country trigger.
PhoneFieldSelectListbox
Country options container. Renders in the top layer via popover="manual", so no portal part is needed.
PhoneFieldSelectOption
Selectable country item.
Composition Order
Typical composition:
PhoneFieldPhoneFieldSelectPhoneFieldSelectComboboxPhoneFieldSelectListboxPhoneFieldInput
Valid Composition Patterns
import {
Label,
PhoneField,
PhoneFieldInput,
PhoneFieldSelect,
PhoneFieldSelectCombobox,
PhoneFieldSelectHiddenSelect,
PhoneFieldSelectIcon,
PhoneFieldSelectListbox,
PhoneFieldSelectOption,
PhoneFieldSelectValue,
} from '@vega-ui/react';The Label names the input, the country trigger gets its own aria-label, and the hidden select submits the country next to the number. Without PhoneFieldSelect the field still formats by code alone.
Invalid Composition Patterns
Input outside PhoneField
This breaks shared formatting and country coordination.
Treating the nested select as unrelated to the input
Country changes must still coordinate with the phone mask and validation model.
Rich option rendering without keeping one selectable row
This weakens keyboard and listbox behavior.