An Angular Material international telephone input with country search, number formatting, validation, masking, localization, and optional GeoIP country detection.
Validation and formatting are powered by
google-libphonenumber.
| ngx-material-intl-tel-input | Angular |
|---|---|
| 22.0.0 - 22.1.0 | 22 |
| 21.0.0 - 21.1.0 | 21 |
| 20.0.0 - 20.1.2 | 20 |
| 19.0.0 - 19.2.1 | 19 |
| 18.0.0 - 18.2.1 | 18 |
| 0.0.1 - 17.3.0 | 17 |
This project is unrelated to ngx-intl-tel-input, ngx-mat-input-tel, and
intl-tel-input.
npm install ngx-material-intl-tel-inputYour application must include an Angular Material theme.
Import the standalone component and ReactiveFormsModule:
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { NgxMaterialIntlTelInputComponent } from 'ngx-material-intl-tel-input';
@Component({
selector: 'app-contact-form',
imports: [ReactiveFormsModule, NgxMaterialIntlTelInputComponent],
templateUrl: './contact-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContactFormComponent {
private readonly formBuilder = inject(FormBuilder);
readonly form = this.formBuilder.group({
phone: ['', Validators.required]
});
}<form [formGroup]="form">
<ngx-material-intl-tel-input fieldControlName="phone"></ngx-material-intl-tel-input>
</form>The component is not a ControlValueAccessor; it does not register
NG_VALUE_ACCESSOR. Do not place formControlName or [formControl] directly
on <ngx-material-intl-tel-input>.
Instead, keep the component inside a parent form container and use one of:
fieldControlName="phone"to resolve the control from the parent[formGroup]; or[fieldControl]="form.controls.phone"to pass the control explicitly.
The supplied control remains the source of truth for its value, validation,
dirty state, and disabled state. The current* events are optional
notifications.
<form [formGroup]="form">
<ngx-material-intl-tel-input [fieldControl]="form.controls.phone" [autoIpLookup]="false"></ngx-material-intl-tel-input>
</form>The component implements the Signal Forms FormValueControl<string> contract,
so it can be bound directly to a field with the [formField] directive β no
fieldControl or parent [formGroup] needed:
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { form, FormField, FormRoot, required, validate } from '@angular/forms/signals';
import { NgxMaterialIntlTelInputComponent, validPhoneNumber } from 'ngx-material-intl-tel-input';
@Component({
selector: 'app-contact-form',
imports: [FormRoot, FormField, NgxMaterialIntlTelInputComponent],
templateUrl: './contact-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContactFormComponent {
readonly model = signal({ phone: '' });
readonly form = form(this.model, (path) => {
required(path.phone);
validate(path.phone, validPhoneNumber);
});
}<form [formRoot]="form">
<ngx-material-intl-tel-input [formField]="form.phone"></ngx-material-intl-tel-input>
</form>The exported validPhoneNumber schema validator checks the emitted
international number with google-libphonenumber and reports an
invalidNumber error kind. Empty values are left to required(). Schema
disabled(...) and required(...) rules are reflected in the Material field,
and the field is marked touched on blur.
| Input | Type | Default | Purpose |
|---|---|---|---|
fieldControl |
FormControl | AbstractControl | null |
FormControl('') |
Explicit form control. |
fieldControlName |
string |
'' |
Control name in the parent form. |
formField |
Field<string> |
β | Binds a Signal Forms field through the FormField directive from @angular/forms/signals. Use instead of fieldControl/fieldControlName. |
value |
string |
'' |
Two-way value backing [formField]; bind it directly only when using the component without any form. |
required |
boolean |
false |
Adds/removes Validators.required. |
disabled |
boolean |
false |
Disables/enables the control and UI. |
initialValue |
string |
'' |
Initial telephone number. |
| Input | Type | Default | Purpose |
|---|---|---|---|
autoIpLookup |
boolean |
false |
Detects the initial country by IP. |
autoSelectCountry |
boolean |
true |
Selects a country during startup. |
autoSelectedCountry |
CountryISO | string |
'' |
Preferred startup country. |
preferredCountries |
(CountryISO | string)[] |
[] |
Pins countries to the top. |
visibleCountries |
(CountryISO | string)[] |
[] |
Restricts available countries. |
excludedCountries |
(CountryISO | string)[] |
[] |
Removes countries from the list. |
localizeCountryNames |
boolean |
false |
Uses locale-aware country names. |
| Input | Type | Default | Purpose |
|---|---|---|---|
numberValidation |
boolean |
true |
Enables libphonenumber validation. |
includeDialCode |
boolean |
false |
Includes the dial code in the input. |
useMask |
boolean |
false |
Enables country-specific input masks. |
forceSelectedCountryCode |
boolean |
false |
Keeps the country code in masked input. |
showMaskPlaceholder |
boolean |
false |
Shows placeholder mask characters. |
outputNumberFormat |
PhoneNumberFormat |
PhoneNumberFormat.INTERNATIONAL |
INTERNATIONAL, E164, or RFC3966. |
enableInputMaxLength |
boolean |
true |
Applies a country-aware max length. Ignored when useMask is enabled, since the mask enforces the exact length. |
| Input | Type | Default | Purpose |
|---|---|---|---|
appearance |
'fill' | 'outline' |
'fill' |
Material form-field appearance. |
enablePlaceholder |
boolean |
true |
Shows the country's number placeholder. |
enableSearch |
boolean |
true |
Enables country search. |
emojiFlags |
boolean |
false |
Uses emoji instead of sprite flags. |
hidePhoneIcon |
boolean |
false |
Hides the call icon. |
iconMakeCall |
boolean |
true |
Makes a valid number icon a tel: link. |
textLabels |
TextLabels |
Built-in English | Replaces visible labels and errors. |
mainLabel |
string |
'' |
Overrides textLabels.mainLabel. |
TextLabels contains:
type TextLabels = {
mainLabel: string;
codePlaceholder: string;
searchPlaceholderLabel: string;
noEntriesFoundLabel: string;
nationalNumberLabel: string;
hintLabel: string;
invalidNumberError: string;
requiredError: string;
numberTooLongError?: string;
};| Event | Payload | Description |
|---|---|---|
currentValue |
string |
Current phone number value. |
currentCountryCode |
string |
Selected dial code, including the leading +. |
currentCountryISO |
string |
Selected lowercase ISO 3166-1 alpha-2 code. |
touch |
void |
Number input blurred; marks the bound Signal Forms field as touched. |
--mat-filled-tel-form-outline-width: Outline width (default: 1px)--mat-filled-tel-form-outline-color: Border color (default: #d8d8d8)--mat-filled-tel-form-background: Background color (default: #fbfbfb)--mat-filled-tel-form-container-shape: Border radius (default: 8px)
--mat-filled-tel-form-focus-outline-color: Focus border color (default: rgb(32, 159, 252))--mat-filled-tel-form-focus-background: Focus background (default: #fff)
--mat-filled-tel-form-hover-background: Hover background (default: #f5f5f5)--mat-outline-tel-form-hover-background: Outline variant hover background (default: #f5f5f5)
--mat-outline-tel-form-background: Outline variant background (default: #fbfbfb)--mat-tel-form-placeholder-color: Input placeholder color (default: #ccc)--mat-tel-form-icon-color: Action icon color (default: #909090)
--mat-outlined-tel-form-container-shape: Outline field border radius (default: Material system variable)--mat-form-field-outlined-container-shape: Text field border radius (default: Material system variable)
--mat-tel-form-hint-color: Hint text color (default: #b2b2b2)--mat-tel-form-error-color: Error message color (default: Material system error)--mat-sys-error: Material system error fallback (default: #f44336)
--mat-theme-primary: Primary theme color (default: rgb(32, 159, 252))--mat-theme-error: Error state color (default: #f44336)--mat-sys-corner-extra-small: Material system small corner radius
:root {
/* Container Customization */
--mat-filled-tel-form-background: #f8f9fa;
--mat-filled-tel-form-container-shape: 6px;
/* Theme Colors */
--mat-theme-primary: #2a7de1;
--mat-tel-form-error-color: #dc3545;
/* Input Styling */
--mat-tel-form-placeholder-color: #a0aec0;
}CI uses Node 24 and npm. After cloning:
npm install
npx nx serve ngx-material-intl-tel-inputCommon checks:
npm run lint:all
npm run unit-tests:all
npm run build:all
npm run e2e:allFor affected projects only:
npx nx affected -t lint test build e2ee2e:all runs the Playwright suite in apps/ngx-material-intl-tel-input-e2e
against the demo app's /e2e harness route; it starts the dev server itself.
Run npx playwright install chromium once before the first run.
The library build is written to
dist/libs/ngx-material-intl-tel-input-lib. Commits follow the Conventional
Commits specification. Test runner constraints are documented in the
Vitest rules.
Country search is accent-insensitive. Enable locale-aware display names per
instance with [localizeCountryNames]="true". The default is the original
English dataset.
When the browser supports
Intl.DisplayNames,
the component resolves names using the active LOCALE_ID. Override individual
countries with COUNTRY_NAME_OVERRIDES:
import { LOCALE_ID } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { COUNTRY_NAME_OVERRIDES } from 'ngx-material-intl-tel-input';
import type { CountryNameOverrides } from 'ngx-material-intl-tel-input';
const spanishCountryOverrides: CountryNameOverrides = {
US: 'Estados Unidos de AmΓ©rica',
MX: 'Estados Unidos Mexicanos'
};
bootstrapApplication(AppComponent, {
providers: [
{ provide: LOCALE_ID, useValue: 'es-ES' },
{
provide: COUNTRY_NAME_OVERRIDES,
useValue: spanishCountryOverrides
}
]
});Thanks goes to these wonderful people:
This project follows the all-contributors specification. Contributions of any kind are welcome!

