Phone Field
Phone number field with consumer-supplied country metadata, pattern formatting, and E.164 output.
| Package | @vizejs/ui/phone-field |
| Maturity | stable |
| Own the source | vize lib pull phone-field |
| Requires | context, controllable-state, id, input-mask, native-select |
| Aliases | phone input, telephone field, international phone, e164 input |
| Covers | HTML tel input, libphonenumber (metadata supplied by consumer), react-phone-number-input |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { PhoneField, PhoneFieldInput, PhoneFieldCountrySelect } from "@vizejs/ui/phone-field";
Or copy the source into your project with vize lib pull phone-field (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Phone number field bound with v-model to an E.164 value, with a country select and pattern formatting. -->
<script setup lang="ts">
import { ref, useId } from "vue";
import {
PhoneField,
PhoneFieldCountrySelect,
PhoneFieldInput,
definePhoneCountries,
} from "@vizejs/ui/phone-field";
const countries = definePhoneCountries([
{ code: "JP", name: "Japan", dialCode: "81", pattern: "99-9999-9999", trunkPrefix: "0" },
{ code: "US", name: "United States", dialCode: "1", pattern: "(999) 999-9999" },
{ code: "GB", name: "United Kingdom", dialCode: "44", pattern: "9999 999999", trunkPrefix: "0" },
]);
const phone = ref("");
const inputId = useId();
</script>
<template>
<div>
<label :for="inputId">Mobile number</label>
<PhoneField
:id="inputId"
v-slot="{ international }"
v-model="phone"
:countries
default-country="JP"
name="phone"
required
>
<PhoneFieldCountrySelect />
<PhoneFieldInput autocomplete="tel-national" />
<output>{{ international }}</output>
</PhoneField>
</div>
</template>
API
PhoneField<Code extends string>
Source: src/families/form/phone-field/phone-field.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
countries (required) |
readonly PhoneCountry<Code>[] |
required |
Supported countries; their code literals type v-model:country. |
modelValue |
string |
undefined |
Controlled E.164 value (+819012345678); "" is empty. undefined selects uncontrolled use. |
defaultValue |
string |
"" |
Initial uncontrolled value (E.164 or national text), also restored by form reset. |
country |
Code |
undefined |
Controlled country code (v-model:country). |
defaultCountry |
Code |
countries[0].code |
Initial uncontrolled country. |
id |
string | null |
undefined |
Id of the number input. null and undefined select a deterministic fallback. |
name |
string |
undefined |
Native form field name; a hidden input submits the E.164 value. |
disabled |
boolean |
false |
Disable the input, the country select, and form submission. |
required |
boolean |
false |
Mark the number as required for native constraint validation. |
ariaLabel |
string |
undefined |
Accessible name of the number input. |
ariaLabelledby |
string |
undefined |
Ids that label the number input. |
ariaDescribedby |
string |
undefined |
Ids that describe the number input. |
ariaErrormessage |
string |
undefined |
Id of the validation error message used while invalid. |
ariaInvalid |
PhoneFieldAriaInvalid |
false |
Invalid state announced to assistive technology. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: string] |
Fired when the E.164 value requests a change. |
update:country |
[code: Code] |
Fired when the country requests a change (v-model:country). |
complete |
[e164: string, country: PhoneCountry<Code>] |
Fired when the national number fills the country pattern. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
PhoneFieldSlotState<Code> |
Renders the country select, number input, and any hints with typed phone state. |
Exposed
| Member | Type | Description |
|---|---|---|
country |
PhoneCountry<Code> |
Selected country. |
nationalNumber |
string |
National significant number (digits only). |
e164 |
string |
E.164 value (+819012345678), or "" while empty. |
formatted |
string |
National number formatted with the country pattern. |
international |
string |
International display form (+81 90-1234-5678), or "" while empty. |
complete |
boolean |
Whether every slot of the country pattern is filled. |
disabled |
boolean |
Whether the field is disabled. |
state |
PhoneFieldState |
Stable state token. |
setCountry |
(code: Code) => boolean |
Select a country, keeping the national digits. |
setValue |
(text: string) => boolean |
Parse and store any national or international text; returns whether it was understood. |
clear |
() => void |
Clear the number. |
root |
— |
PhoneFieldInput
Source: src/families/form/phone-field/phone-field-input.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder |
string |
undefined |
Native placeholder; defaults to nothing so consumers can show a sample number. |
autocomplete |
string |
"tel-national" |
Autocomplete token: tel-national beside a country select, tel when international text is expected. |
PhoneFieldCountrySelect
Source: src/families/form/phone-field/phone-field-country-select.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
ariaLabel |
string |
"Country" |
Accessible name of the country select. |
getOptionLabel |
(country: PhoneCountry) => string |
(country) => |
Option text for a country. |
Behavior
Normative state x input -> outcome table for phone-field.vue,
phone-field-input.vue, and phone-field-country-select.vue
(@vizejs/ui/phone-field). No country data is bundled: consumers pass
countries (typed with definePhoneCountries), whose code literals type
v-model:country. Formatting reuses the input-mask engine; the country picker
reuses NativeSelect. Every row is proven by the named test in
phone-field.test.ts or phone-field-ssr.test.ts; compile-only assertions live
in phone-field.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| PH1 | helpers | parse / format / E.164 | national text drops the trunk prefix; +/00 text picks the longest dial code (ties prefer the current country); full-width digits fold |
parses national and international text with trunk prefixes and dial-code matching |
| PH2 | seeded, named | render | type="tel" input (inputmode="tel", tel-national), NativeSelect country picker with aria-controls, hidden E.164 value |
renders a tel input, a NativeSelect country picker, and a hidden E.164 value |
| PH3 | empty | typing | digits format by the country pattern, the trunk prefix is stripped, complete(e164, country) fires when the pattern fills |
typing formats by the country pattern, strips the trunk prefix, and emits completion |
| PH4 | number entered | country select | the national digits are kept and re-targeted to the new dial code; update:country fires |
choosing a country keeps the digits and re-targets the dial code |
| PH5 | any | international typing / paste | +44… or pasted +1… switches the country and fills the national number |
international input and paste switch the country automatically |
| PH6 | controlled / API | model / expose | a stored number's country wins over the selected country; setValue, setCountry, clear |
controlled values from another country win, and the API sets, switches, and clears |
| PH7 | in a form / disabled | reset / edits | form reset restores defaults; disabled fields disable input and select and ignore edits | form reset restores defaults and disabled fields ignore input |
| PH8 | invalid setup | empty countries / no root | throws VIZE_UI_PHONE_FIELD_COUNTRIES or VIZE_UI_CONTEXT_MISSING: PhoneField |
rejects empty country lists and parts outside a PhoneField |
| PH9 | SSR / hydration | isolated requests | byte-identical markup with the formatted number and selected country; hydration without diagnostics | renders byte-identical phone markup and hydrates without mismatches |
Public extension contract
| Surface | Contract |
|---|---|
| Parts | root (group), input, country-select. |
| Data attributes | data-vize-ui, root data-state (empty/incomplete/complete/disabled) and data-country; input data-dial-code. |
| Metadata | PhoneCountry { code, name, dialCode, pattern?, trunkPrefix? }, supplied by the consumer. |
The subpath is tree-shakable and ships no CSS; those package contracts are
pinned by distribution.test.ts, check:size, and check:tree-shaking.