Phone
The <x-wirekit::phone> component captures an international phone number. A reader picks their country and types the number the way they would write it down; the value your form receives is E.164, the international format APIs and databases expect.
The two are different strings, and that is the whole point of the component. The box shows 0151 2345 6789; the form receives +4915123456789. Every example below shows both.
Basic Usage
Type 0151 2345 6789 and the form submits +4915123456789. The leading 0 is a German national-format digit that E.164 does not carry, so it is dropped — and it is not dropped for a country that has no such digit. An Italian number entered as 06 6982 submits as +39066982, zero intact, because in Italy that zero is part of the number.
Choosing Which Countries to Offer
countries narrows the picker. It does not restrict what can be typed: a reader pasting a number from somewhere else is followed rather than corrected.
Pick Austria and type 0664 1234567, and the form receives +436641234567.
Two of the codes the picker offers have no flag artwork and draw a neutral placeholder instead: AC (Ascension Island) and TA (Tristan da Cunha). Both are exceptional-reservation codes rather than countries, and the artwork set this component reads does not carry them. Nothing else changes — the dialing code, the name and the formatting all work.
countryOrder keeps the whole list and pulls a few entries to the top:
The pair holds wherever you scroll to: 020 7946 0958 under United Kingdom submits as +442079460958.
Binding to Livewire
{{-- 1. The property receives E.164 — `+4915123456789`, never what is in the box. --}}
<x-wirekit::phone name="phone" label="Phone number" wire:model="phone" />
// 2. Validate on the server. The field formats; it does not decide whether a number exists.
$this->validate([
'phone' => ['required', 'string', 'max:20'],
]);
The component never calls a number invalid. Deciding that needs per-country length and range data this package does not carry, and a field that quietly rejects a real number is worse than one that never claims to know. Use the error prop to show what your server decided.
When you do need that decision, Validating a Phone Number is the recipe: one composer require, one validation rule, and the same library formatting the stored value back for display.
Help Text
We only use this to confirm a delivery.
A hint changes neither string: 0151 2345 6789 in the box, +4915123456789 in the form.
Error State
We could not reach this number.
An error changes neither string either. The box keeps 0151 2345 6789 and the form still receives +4915123456789 — the component styles the rejection your server decided on, and never decides one itself.
Starting From a Stored Value
An existing E.164 value splits back into a country and a number, so editing a saved record shows a phone number rather than a raw string:
Personalization Blocks
| Block | Default | What it styles |
|---|---|---|
group |
flex min-w-0 items-stretch gap-2 |
The row holding both controls |
country |
wk-field shrink-0 max-w-[10rem] |
The country picker |
number |
wk-field min-w-0 grow |
The number box |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
country |
string |
'DE' |
The country the field starts on, as an ISO 3166-1 alpha-2 code. Case does not matter |
countries |
array|null |
null |
Which countries the picker offers. null offers every country the dialing-code table carries. Narrows the picker only — it does not restrict what can be typed |
countryOrder |
array |
[] |
Codes pulled to the top of the list, in the order given |
label |
string|null |
null |
Label above the field. Also names the group, so assistive technology announces the field once and then each of its two controls |
hideLabel |
bool |
false |
Render the label visually hidden but keep it for assistive tech |
hint |
string|null |
null |
Help text below the field |
error |
string|null |
null |
Error message (also reads from $errors) |
announceError |
bool|null |
null |
Render the error in an aria-live="polite" region so a validation error that appears after render reaches a screen reader |
name |
string|null |
null |
Form field name. The E.164 value is submitted under it |
id |
string|null |
auto-generated | Element id |
size |
string |
'md' |
'sm', 'md', 'lg' |
placeholder |
string|null |
null |
Hint text inside the number box |
required |
bool |
false |
Marks the number box required |
disabled |
bool |
false |
Disabled state |
readonly |
bool |
false |
Keeps the value without allowing edits |
scope |
string|null |
null |
Scoped personalization key |
Accessibility
This is two controls that submit one value, and it is built as one. The country picker and the number box are separate tab stops inside a group named by the field's label, so a screen reader announces the field once and then each part.
- The NUMBER BOX is deliberately not a combobox. It is free text, and a listbox relationship on it would promise a keyboard model it does not have. The country picker beside it is one, and the two are not linked: they are separate controls that happen to submit one value.
- The dialing code is readable, not merely visible. Each row in the picker names the country and its code —
Germany (+49). A bare+49glyph beside the box is invisible to a reader who cannot see it, and the number then reads as a fragment with no country attached. - The flag is decorative and marked as such. Every row names its country in text, so the artwork adds nothing for a reader who cannot see it and would otherwise read the country name twice.
autocomplete="tel"on the number box, so a browser fills the field it means to.- Nothing is rewritten while you type. A half-finished number is the normal state of this field for as long as it is in use.
Keyboard Interaction
Two tab stops. The number box invents nothing at all, and the picker keeps the combobox model with exactly one deliberate deviation: choosing a country moves focus on to the number box rather than back to the picker, because that is always the next thing to do.
| Key | Where | What happens |
|---|---|---|
| Tab | anywhere in the field | Moves between the country picker and the number box, in that order |
| Up / Down | country picker | Opens the list, and moves through it |
| type anything | country picker | Filters the countries by name or dialing code |
| Enter | country picker | Takes the highlighted country, and focus moves on to the number box |
| Esc | country picker | Closes the list and leaves the country as it was |
| any character | number box | Is kept exactly as typed — nothing is reformatted under the caret |
The country picker is a combobox, which is what lets each row carry its flag: an <option> holds no image, so a native select could not show one however the artwork was shipped. What it gives up is the platform's own country wheel, and the search field is the trade — with well over two hundred countries, typing three letters beats scrolling.
Two behaviors here are this component's rather than the combobox's, and both are deliberate:
- Choosing a country moves focus to the number box. A combobox normally returns focus to itself, which is right for a menu. Here the next act is always to keep typing the number.
- The country cannot be cleared. Its dialing code is what assembles the value, so a field with no country would show a number on screen and submit an empty string.
Pitfalls
- Do not assert on what the box shows. A test, a preview or an example that checks the displayed string is checking the wrong one of the two.
- Do not strip a leading zero yourself. Whether it belongs to the number depends on the country, and the component already knows which.
- A country is state the field holds, not something it infers. Several countries share a dialing code, so a number alone cannot say which one it belongs to.
Customization
Set a project-wide default for the country a field starts on, or its size:
// config/wirekit.php
'components' => [
'phone' => ['country' => 'AT', 'size' => 'sm'],
],
A form that expects a different country still says so on the field; this only moves where the others start. Setting it to the country most of your readers are in saves one interaction on most of your forms, and costs nothing on the rest.
Further Reading
- Validating a Phone Number — deciding whether a well-formed number is a real one, on the server where that decision belongs
- Input — the single-control text field this one sits beside
- Select — for a country field with no phone number attached