lianmaymesi / livewire-phone
Phone input package for Laravel and Livewire applications.
Requires
- php: ^8.2
- giggsey/libphonenumber-for-php: ^9.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/validation: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- livewire/blaze: ^1.0
- livewire/livewire: ^4.2
- spatie/laravel-package-tools: ^1.93
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
lianmaymesi/livewire-phone is a Laravel and Livewire-oriented phone input package built on top of intl-tel-input.
Goals
This package is meant to be a Flux-friendly, Livewire-first Laravel wrapper around the full intl-tel-input feature set, while also exposing Laravel validation and package publishing ergonomics.
Install flow
composer require lianmaymesi/livewire-phone php artisan livewire-phone:install
The install command:
- publishes package config and translations
- publishes Vite source assets into
resources/vendor/livewire-phone - runs
npm install intl-tel-input - adds the package Vite entries to
vite.config.js
The generated Vite entry files are:
resources/vendor/livewire-phone/livewire-phone.jsresources/vendor/livewire-phone/livewire-phone.css
Your custom overrides load after the package base files:
resources/vendor/livewire-phone/phone.custom.jsresources/vendor/livewire-phone/phone.custom.css
The component injects its CSS and JavaScript automatically with Livewire / Blaze
@assets, so you do not need to place manual <script> or <link> tags in
your page when using the component.
Source vs dist
The package keeps its editable frontend source in:
resources/stubs/vite/phone.jsresources/stubs/vite/phone.css
These files stay readable and are intended for maintenance.
The minified files used by asset_driver = "public" are generated into:
resources/dist/livewire-phone.jsresources/dist/livewire-phone.css
To rebuild the distributed assets from source:
npm install npm run build:dist
The CSS build is PostCSS-based and Tailwind-ready, so you can introduce @apply
later without changing the dist workflow.
Feature coverage
| Feature | Status | Notes |
|---|---|---|
| Auto-select user's current country via IP lookup | Supported | Enable geoip.enabled and provide geoip.endpoint in config. |
| Example placeholder for selected country | Supported | Driven by upstream autoPlaceholder. |
| Type-ahead and keyboard dropdown navigation | Supported | Provided by intl-tel-input with countrySearch. |
| Format as user types | Supported | Enabled by default and configurable. |
| Numeric-only input and max valid length cap | Supported | Enforced by package JS and upstream strictMode. |
| National input converted to international standard number | Supported | Package syncs formatted output and metadata. |
| Validation with specific error types | Supported | Laravel rule plus hidden metadata inputs/events for client-side error codes. |
| High-resolution flag images | Supported | Bundled through the npm package and Vite asset pipeline. |
| Accessibility via ARIA | Supported | Upstream behavior preserved; package adds error aria-describedby. |
| TypeScript type definitions | Planned | Best shipped through a companion npm package, not the PHP package alone. |
| CSS variable overrides and dark mode styling | Supported | Flux-compatible CSS and override-friendly selectors included. |
| React, Vue, Angular, and Svelte components | Planned | These belong in companion JS packages/adapters. |
| 40+ translations, RTL, alternative numerals | Partially supported | Upstream supports this; Laravel translations are included now, deeper JS i18n is a next phase. |
| Rich init options, methods, events | Supported | Component passes init options and emits livewire-phone:change. |
Current package foundation
- Blade component for Livewire forms:
<x-livewire-phone::phone /> - Package-managed frontend assets
- Single-property payload submission for both plain forms and Livewire
- Laravel validation rule:
new \Lianmaymesi\LivewirePhone\Rules\PhoneNumber() - Optional Eloquent casts for normalized string and object storage
- Validator extension:
phone_number - Publishable config, language files, and public assets
- Multi-language-ready translation structure
- Flux-compatible field rendering and styles
- Optional geo-IP country lookup
- Client event:
livewire-phone:change
Example usage
<x-livewire-phone::phone wire:model.live="phone" name="phone" label="{{ __('Phone') }}" initial-country="in" only-countries="in" format="e164" :geo-ip-lookup="true" />
use Lianmaymesi\LivewirePhone\Rules\PhoneNumber; Validator::make($data, [ 'phone' => ['required', new PhoneNumber('IN', ['IN'])], ]);
Flux usage
When Flux is installed, the component renders through flux:with-field so it behaves like the rest of your Flux and Flux Pro form fields.
Typical Flux usage:
<x-livewire-phone::phone wire:model.live="phone" label="Phone" name="phone" format="e164" />
Supported field-style props:
labelshows the Flux field labelinvalidforces the error statevariant="borderless"uses the borderless input treatmentplaceholderoverrides the automatic example placeholderdisabled,required,autofocus, andautocompleteare forwarded to the input
If Flux is not installed, the component falls back to the package field wrapper and still works the same way.
Format and output modes
Use the format prop to control both the displayed phone format and the value pushed back into Livewire or submitted through a normal form. format is preferred over value so it does not clash with the native HTML value attribute.
String output modes:
format="e164"returns one normalized string such as+14155552671format="national"returns one normalized string with the national digits onlyformat="significant"returns one normalized string with the local mobile digits onlyformat="string:e164",format="string:national", andformat="string:significant"are explicit aliases for string output
Object output modes:
format="object"returns one JSON string object and defaultsnumbertoe164format="object:e164"returns the object withnumberas normalized international formatformat="object:national"returns the object withnumberas normalized national digitsformat="object:significant"returns the object withnumberas normalized local digits
For backward compatibility, value is still accepted as a fallback alias.
The package submits only one field for the phone value. It does not create extra phone_country, phone_valid, phone_error, or phone_type form properties.
If you do not pass a custom placeholder, the component updates the example placeholder based on the selected format:
e164shows an international-style examplenationalshows a national examplesignificantshows the core local mobile number example
Example object payload:
{
"number": "+919944712499",
"phone_number": "9944712499",
"dial_code": "+91",
"country_code": "IN"
}
This object format is designed to stay under one property while still giving you the normalized pieces you usually want to store:
numberis the normalized value for the selected modephone_numberis always the plain local number without spaces or special charactersdial_codeis the country calling code such as+91country_codeis the ISO country code such asIN
The server-side normalizer also accepts mixed existing values such as:
+91 99447 1249999447-12499{"number":"+919944712499","country_code":"IN"}{"phone_number":"9944712499","dial_code":"+91"}
So you can store or hydrate values whether the input came with spaces, punctuation, IN, or +91.
Single-property behavior
String mode example:
<x-livewire-phone::phone name="phone" format="significant" :only-countries="['in']" />
Submitted value:
'phone' => '9944712499'
Object mode example:
<x-livewire-phone::phone name="phone" format="object:e164" :only-countries="['in']" />
Submitted value:
'phone' => '{"number":"+919944712499","phone_number":"9944712499","dial_code":"+91","country_code":"IN"}'
Casting helpers
If you want the same normalization on save and retrieve in Eloquent, the package provides two casts:
Lianmaymesi\LivewirePhone\Casts\AsPhoneStringLianmaymesi\LivewirePhone\Casts\AsPhoneObject
Example:
use Lianmaymesi\LivewirePhone\Casts\AsPhoneObject; use Lianmaymesi\LivewirePhone\Casts\AsPhoneString; protected function casts(): array { return [ 'phone' => AsPhoneString::class . ':significant,IN', 'phone_meta' => AsPhoneObject::class . ':e164,IN', ]; }
This lets the model accept values like 99447 12499, +91 99447 12499, or a JSON object payload and keep them normalized consistently.
Common prop reference
The main component props are:
initial-country="us"sets the default selected countrycountry-order="us,ca,mx"controls dropdown orderingonly-countries="us,ca,mx"restricts the selectable countriesformat="e164|national|significant|object[:...]"controls the output shape:geo-ip-lookup="true"enables country auto-detection when configured:options="[]"overrides plugin options directly
Common plugin-related props:
allow-dropdowncountry-searchfix-dropdown-widthformat-as-you-typeformat-on-displayshow-flagsstrict-modeuse-fullscreen-popupnational-modeseparate-dial-codeallow-phonewordsallow-number-extensions
Single vs multiple countries
The only-countries and country-order props accept either:
- a single ISO2 country code string
- a comma-separated string of ISO2 country codes
- a PHP array of ISO2 country codes
Use a single value when the field should stay locked to one country:
<x-livewire-phone::phone wire:model.live="phone" name="phone" initial-country="in" only-countries="in" />
You can also pass the single country as an array:
<x-livewire-phone::phone wire:model.live="phone" name="phone" :only-countries="['in']" :country-order="['in']" />
For multiple allowed countries, pass an array:
<x-livewire-phone::phone wire:model.live="phone" name="phone" initial-country="us" :only-countries="['us', 'ca', 'mx']" :country-order="['us', 'ca', 'mx']" />
If your Livewire or controller data starts as objects, map them to ISO2 strings before passing them in:
$onlyCountries = collect($countries) ->pluck('iso2') ->map(fn ($country) => strtolower($country)) ->values() ->all();
<x-livewire-phone::phone wire:model.live="phone" name="phone" :only-countries="$onlyCountries" />
The component expects country codes like us, in, gb, not full country objects.
Validator extension
'phone' => ['required', 'phone_number:IN,IN']
The first parameter is the default parse region. The remaining parameters restrict allowed regions.
Config highlights
return [ 'initial_country' => 'auto', 'default_value' => 'e164', 'options' => [ 'format_as_you_type' => true, 'strict_mode' => true, 'country_search' => true, 'auto_placeholder' => 'polite', ], 'geoip' => [ 'enabled' => true, 'endpoint' => 'https://ipapi.co/json', 'country_key' => 'country_code', ], ];
Runtime metadata
It also dispatches a browser event:
window.addEventListener("livewire-phone:change", (event) => { console.log(event.detail); });