Skip to content
v2.5.0
English
Русский
Documentation

PhoneField

PhoneField is a compound component that combines a masked phone input with a nested country-code Select.
phone-field.tsx · 8 lines
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
Tested in
25 tests

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

  • PhoneFieldProps
  • PhoneFieldInputProps
  • PhoneFieldSelectProps

Minimal Composition

minimal-composition.tsx · 38 lines
import {
  Label,
  PhoneField,
  PhoneFieldInput,
  PhoneFieldSelect,
  PhoneFieldSelectCombobox,
  PhoneFieldSelectHiddenSelect,
  PhoneFieldSelectIcon,
  PhoneFieldSelectListbox,
  PhoneFieldSelectOption,
  PhoneFieldSelectValue,
} from '@vega-ui/react';

Required Parts

  • PhoneField: root field state and formatting context
  • PhoneFieldInput: actual phone number input

Optional Parts

  • PhoneFieldSelect*: nested country-code picker family built on the same select subsystem contract
  • PhoneFieldSelectHiddenSelect: native form participation for country code

Composition Order

Typical composition:

  1. PhoneField
  2. PhoneFieldSelect
  3. PhoneFieldSelectHiddenSelect
  4. PhoneFieldSelectCombobox
  5. PhoneFieldSelectListbox
  6. PhoneFieldSelectOption
  7. PhoneFieldInput

Variants

  • Input-only usage versus full country-select composition
  • strictMask versus 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.

simple-iso-based-phone-mask.tsx · 15 lines
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.

select-with-country-options-and-icons.tsx · 62 lines
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.

controlled-value-with-clear-action.tsx · 25 lines
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.

uncontrolled-native-form-submit.tsx · 53 lines
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.

full-field-inside-a-dialog-form.tsx · 85 lines
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.

invalid-number-state.tsx · 52 lines
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.

disabled-input.tsx · 59 lines
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.

non-strict-mask-for-lenient-paste-behavior.tsx · 43 lines
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.

emoji-flags.tsx · 67 lines
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:

  1. PhoneField
  2. PhoneFieldSelect
  3. PhoneFieldSelectCombobox
  4. PhoneFieldSelectListbox
  5. PhoneFieldInput

Valid Composition Patterns

minimal-composition.tsx · 38 lines
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.