Frontend
Blade component
<x-phone-input
name="phone"
:value="old('phone', $user->phone)"
country="AE"
:preferred="['AE', 'SA', 'PK']"
only="AE,SA,PK,GB,US"
types="mobile"
required
/>
| Attribute | Default | |
|---|---|---|
name |
— | Input name. The widget also posts <name>_country. |
value |
old(name) |
String or PhoneNumber. E.164 picks the right country. |
country |
phone.default_country, else detected |
Initial country |
preferred |
phone.input.preferred |
Pinned to the top of the list |
only |
all | Restrict the list |
types |
any | Live validation for these types, e.g. mobile |
country-name |
<name>_country |
Name of the hidden country input |
locale |
app locale | Country names and messages |
theme |
auto |
light, dark or auto |
validate-on |
blur |
blur, input or off |
dial-code |
separate |
inline keeps +971 inside the text |
Any other attribute (id, class, placeholder, required, wire:model, aria-*) goes on the
<input>.
What it renders
A real <input type="tel" autocomplete="tel"> with the number already formatted, so the form works
before JavaScript loads — or without it. The widget then mounts on it. Browser messages come from the
package's translations, so they match the server's wording in the user's language.
Styling
The field is Phone Input, so everything in its
styling guide applies: CSS variables, classNames, the
@layer stylesheet, and dark mode that follows your page's .dark or data-theme.
CSS variables in your app's stylesheet:
.dxp, .dxp-panel {
--dxp-accent: #4f46e5;
--dxp-radius: 6px;
--dxp-height: 40px;
}
Classes on every part: once for all fields in config/phone.php, or per field:
'input' => [
'class_names' => ['root' => 'shadow-sm', 'panel' => 'rounded-xl shadow-xl'],
],
<x-phone-input name="phone" class="font-mono" :widget="['classNames' => ['panel' => 'ring-1 ring-black/5'], 'flags' => 'emoji']" />
class and any other HTML attribute go on the <input>. widget takes any
Phone Input option, merged over the component's own.
Keep your own field: if your app already styles every input (Tailwind Forms, a design system, Filament), let the widget float its country button inside your input instead of wrapping it:
<x-phone-input name="phone" class="form-input w-full" :widget="['layout' => 'overlay']" />
The input keeps its classes, borders and focus ring; only the country button and the dropdown come from the widget. See the overlay layout.
The markup itself:
php artisan vendor:publish --tag=phone-views
This copies the component to resources/views/vendor/phone/components/phone-input.blade.php, where you
can wrap it, add a label and error slot, or change how assets load.
Assets
By default the component loads Phone Input from the Devix CDN, pinned to the release this package was tested with, once per page. To bundle it yourself instead:
PHONE_ASSETS_URL=
import { createPhoneInput } from '@devix-labs/phone-input';
import '@devix-labs/phone-input/styles.css';
document.querySelectorAll('input[data-devix-phone]').forEach((input) =>
createPhoneInput(input, JSON.parse(input.dataset.devixPhone)),
);
Livewire
The field is wrapped in wire:ignore, so Livewire's DOM updates don't reset the widget. Fields added
later — Livewire, Turbo, modals — are mounted automatically.
wire:model is bound to a hidden input that receives the full number: E.164 once the number is
complete, and the typed text with its calling code before that (+92 30). The rule needs no
country field either way, and an unfinished number gets a useful message (too short):
<x-phone-input name="whatsapp" country="PK" wire:model.live="whatsapp" />
public string $whatsapp = '';
protected function rules(): array
{
return ['whatsapp' => ['required', Phone::rule()->mobile()]];
}
Inertia, React and Vue
Use the widget's own wrapper and send phone and phone_country with the form. The same rule
validates it:
import { PhoneInput } from '@devix-labs/phone-input/react';
<PhoneInput
value={data.phone}
initialCountry="AE"
onChange={(e164, result) => setData({ ...data, phone: e164 ?? '', phone_country: result.region ?? '' })}
/>
Posting E.164 means the country field is optional. Send it anyway, so an unfinished number is still read with the right country and the error explains what's wrong.