If you’ve ever had to build a sign-up form, user profile editor, or checkout page, you know that international telephone inputs are a notorious engineering headache.
To build a truly production-ready phone input, you must solve a mountain of requirements:
- Internationalization: Support for 200+ countries, custom dial codes, and localized names.
-
Formatting & Validation: Parsing input on-the-fly (e.g., formatting
2025550143to(202) 555-0143as the user types) and validating the number against strict E.164 specifications. - Accessibility (a11y): Proper WAI-ARIA combobox semantics, full keyboard navigation (arrows, escape, enter), and screen-reader friendliness.
- Contact Autocomplete: Integrating with backend APIs to search existing contacts/customers and autocomplete their details from a unified input field.
Faced with these challenges, we created ng-tel-input-autocomplete — a lightweight, feature-rich, standalone Angular 21 component designed to make international phone entry effortless without sacrificing accessibility or customization.
In this blog, we’ll explore the design patterns behind the library, its implementation details, and how you can integrate it into your Angular projects today.
The Architecture: Under the Hood of ng-tel-input-autocomplete
To build a robust control, we combined three core technologies:
- Angular 21 & Signals: State management is entirely signal-powered, facilitating fine-grained reactivity, fast change detection, and native support for Angular 21's reactive form features.
-
Angular CDK Overlay: Dropdowns are constructed using the
@angular/cdk/overlaypackage. This ensures the panel floats correctly above content, manages viewport boundaries gracefully, and closes on outside click or scroll. -
Google's
libphonenumber&intl-tel-input: Metadata for phone formats, validation states, and dial codes are managed via optional peer-dependencies. If installed, the library automatically enables offline E.164 parsing, formatting, and granular error reasons (e.g.,TOO_SHORT,INVALID_COUNTRY_CODE).
Key Features
- Standalone & Lightweight: Built for modern Angular, containing no legacy modules or unnecessary dependencies.
- Built for Accessibility: WAI-ARIA compliant listbox/combobox layout. Keyboard navigation is fully supported.
- Contact Search & Autocomplete: Switch between typing a phone number or searching contacts. Query string emits to your service, displaying custom suggestions in the overlay.
-
Paginated Suggestion Lists: Emits scroll-end events (
loadMoreSuggestions) to let you fetch and append next-page results asynchronously. - Complete Styling Freedom: Fully customizable with design-system-friendly CSS variables (Theme Tokens) or custom template-refs for flags, list items, and empty/loading states.
Step-by-Step Integration
Let's walk through how to install, configure, and use the component in a clean Angular 21 project.
1. Installation
Install the package and its peer dependency, Angular CDK:
npm install ng-tel-input-autocomplete @angular/cdk
(Optional but highly recommended) Install google-libphonenumber and intl-tel-input for offline phone validation and automatic display formatting:
npm install google-libphonenumber intl-tel-input
2. Configure Global Defaults
Instead of repeating configuration options on every input, you can define application-wide defaults in your application config (e.g., app.config.ts):
import { ApplicationConfig } from '@angular/core';
import { provideNgTelInputAutocomplete } from 'ng-tel-input-autocomplete';
export const appConfig: ApplicationConfig = {
providers: [
provideNgTelInputAutocomplete({
defaultCountry: 'IN',
flagMode: 'emoji', // 'emoji' or 'image' (resolves to FlagCDN)
validationEnabled: true, // Enables libphonenumber validation
preferredCountries: ['IN', 'US', 'GB'],
outputFormat: 'string', // 'string' (E.164) or 'object' (structured values)
}),
],
};
3. Integrate in a Reactive Form
Simply import NgTelInputAutocomplete and bind it to an Angular FormControl like any standard input component:
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { NgTelInputAutocomplete, PhoneInputValue } from 'ng-tel-input-autocomplete';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [ReactiveFormsModule, NgTelInputAutocomplete],
template: `
<form class="form-container">
<label for="phone-input">Phone Number</label>
<ng-tel-input-autocomplete inputId="phone-input" [formControl]="phone" />
@if (phone.invalid && phone.dirty) {
<span class="error-msg">Please enter a valid phone number.</span>
}
</form>
`,
styles: [
`
.form-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 320px;
}
.error-msg {
color: #ef4444;
font-size: 0.85rem;
}
`,
],
})
export class ContactFormComponent {
readonly phone = new FormControl<PhoneInputValue>(null, [Validators.required]);
}
Asynchronous Contact Autocomplete
One of the standout features of ng-tel-input-autocomplete is its ability to handle external contact search. When a user types a name instead of a number, the component detects the alphabetical input and triggers search events. You can fetch suggestions from your service and feed them back to the input.
Here is how to set up an asynchronous contact suggestion search with paginated infinite scroll:
import { Component, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { NgTelInputAutocomplete, PhoneSuggestion } from 'ng-tel-input-autocomplete';
import { ContactService } from './contact.service';
@Component({
selector: 'app-phone-search',
standalone: true,
imports: [ReactiveFormsModule, NgTelInputAutocomplete],
template: `
<ng-tel-input-autocomplete
[formControl]="phone"
[suggestions]="suggestions()"
[suggestionsLoading]="loading()"
[suggestionsExhausted]="exhausted()"
(suggestionSearch)="onSearch($event)"
(loadMoreSuggestions)="onLoadMore()"
/>
`,
})
export class PhoneSearchComponent {
readonly phone = new FormControl(null);
readonly suggestions = signal<PhoneSuggestion[]>([]);
readonly loading = signal(false);
readonly exhausted = signal(false);
private currentQuery = '';
private currentPage = 1;
constructor(private contactService: ContactService) {}
onSearch(query: string) {
this.currentQuery = query;
this.currentPage = 1;
this.suggestions.set([]);
this.exhausted.set(false);
if (!query.trim()) return;
this.loading.set(true);
this.contactService.search(query, this.currentPage).subscribe({
next: (results) => {
this.suggestions.set(results);
this.loading.set(false);
},
error: () => this.loading.set(false),
});
}
onLoadMore() {
if (this.loading() || this.exhausted()) return;
this.currentPage++;
this.loading.set(true);
this.contactService.search(this.currentQuery, this.currentPage).subscribe({
next: (newResults) => {
if (newResults.length === 0) {
this.exhausted.set(true);
} else {
this.suggestions.update((prev) => [...prev, ...newResults]);
}
this.loading.set(false);
},
error: () => this.loading.set(false),
});
}
}
Customizing Design and Themes
ng-tel-input-autocomplete doesn't enforce heavy styles. It ships with a clean, modern aesthetic that adapts to your environment using CSS Custom Properties.
You can override variables in your global stylesheet to align the library with your design system or brand guidelines:
/* Customizing the component theme */
ng-tel-input-autocomplete {
--ngti-radius-md: 8px;
--ngti-color-border: #cbd5e1;
--ngti-color-primary: #6366f1; /* Brand primary */
--ngti-color-primary-soft: #eeeffe;
/* Size overrides */
--ngti-size-input-height: 48px; /* Touch target size */
}
/* Outlined and filled variants supported natively */
ng-tel-input-autocomplete[variant='filled'] {
--ngti-color-surface: #f1f5f9;
}
For advanced markup customization, you can pass custom templates for the country items, selected country trigger, or suggestion list:
<ng-tel-input-autocomplete [formControl]="phone" [countryTemplate]="customCountry">
<ng-template #customCountry let-country>
<div class="flex items-center gap-2">
<span>{{ country.flag }}</span>
<strong>{{ country.name }}</strong>
<span class="text-muted">+{{ country.dialCode }}</span>
</div>
</ng-template>
</ng-tel-input-autocomplete>
Accessibility First
In modern applications, accessibility is non-negotiable. Building custom dropdown overlays that screen readers can interpret is notoriously difficult. ng-tel-input-autocomplete solves this out of the box:
-
WAI-ARIA Compliance: Implements the
comboboxrole on the input,listboxon the dropdown overlay, andoptionon each item. -
Keyboard Navigation: Arrows select items,
Enterconfirms, andEscapecloses the dropdown. - Screen Reader Feedback: Dynamic announcements are made when a search is loading, empty, or returns results, ensuring a seamless experience for visually impaired users.
Wrapping Up
Whether you are building a simple contact form or a complex CRM panel, ng-tel-input-autocomplete provides the robustness, accessibility, and performance needed for modern web development.
Check out the links below to start using it in your next Angular project:
- npm Package: ng-tel-input-autocomplete
- GitHub Repository: Subhrangsu90/tel-input-autocomplete
- Full Documentation: GitBook Reference
- Live Playground: Vercel Demo
Have any questions, issues, or feature requests? Head over to the GitHub Issues page and let us know!
Top comments (0)