npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ngx-intl-phone-input

v0.3.2

Published

Accessible, headless Angular 17 international phone input with country selector

Readme

ngx-intl-phone-input

Accessible, headless Angular 17 international phone input with CDK-powered country selector.

Open in StackBlitz

Built as a drop-in replacement for ngx-intl-tel-input, with fixes for known validation bugs with Suriname (+597) and Russia/Kazakhstan (+7) numbers.

Installation

npm install ngx-intl-phone-input

Install peer dependencies if not already present:

npm install @angular/cdk angular-imask imask

Add the CDK prebuilt overlay styles to your angular.json:

"styles": ["node_modules/@angular/cdk/overlay-prebuilt.css", "src/styles.scss"]

Or import directly in your global stylesheet:

@import '@angular/cdk/overlay-prebuilt.css';

Usage

Import PhoneInputComponent into your standalone component (or NgModule):

import { PhoneInputComponent } from 'ngx-intl-phone-input';

@Component({
  standalone: true,
  imports: [FormsModule, PhoneInputComponent],
  ...
})

Use it in your template:

<ngx-intl-phone-input
  [(ngModel)]="phone"
  [defaultCountry]="'BR'"
  [locale]="'pt-BR'"
  placeholder="Telefone"
  (countryChange)="onCountryChange($event)"
  (phoneStatus)="onPhoneStatus($event)"
/>

The phone value is always an E.164 string (e.g. "+5511987654321") when valid, or null when empty or invalid.

Inputs

| Input | Type | Default | Description | |---|---|---|---| | defaultCountry | CountryCode | 'US' | ISO 3166-1 alpha-2 code for the initially selected country | | placeholder | string | '' | Placeholder text for the phone number input | | locale | SupportedLocale | 'en-US' | Locale for country names and UI strings (see Localization) |

Outputs

| Output | Payload | Description | |---|---|---| | countryChange | CountryData | Fires when the user selects a different country | | phoneStatus | PhoneStatus | Fires on every keystroke with parse details |

PhoneStatus

interface PhoneStatus {
  isPossible: boolean; // number length is plausible for the selected country
  isValid: boolean;    // fully valid E.164 number
  e164: string | null; // E.164 value when valid, otherwise null
}

Validation

The component registers itself as an Angular validator. Invalid numbers set an invalidPhone error on the control:

// Template-driven
<ngx-intl-phone-input [(ngModel)]="phone" #phoneCtrl="ngModel" />
<span *ngIf="phoneCtrl.errors?.['invalidPhone']">Invalid phone number</span>

// Reactive forms
const ctrl = new FormControl('');
// ctrl.errors → { invalidPhone: true }

Empty values are not flagged as invalid — combine with Validators.required if needed.

Localization

The locale input translates country names in the dropdown and the search field placeholder.

| Locale | Language | Search placeholder | |---|---|---| | en-US | English (default) | Search countries… | | pt-BR | Portuguese (Brazil) | Buscar países… |

<ngx-intl-phone-input [locale]="'pt-BR'" ... />

Country search works in the active locale — typing "bras" in pt-BR matches "Brasil".

Keyboard navigation

The country dropdown is fully keyboard accessible:

| Key | Action | |---|---| | Space / Enter / | Open dropdown | | / | Navigate options | | Enter | Select focused option (focus moves to phone input) | | Escape | Close without changing selection (focus returns to trigger) | | Any letter/digit | Type-ahead search within the list |

Styling

The library is fully headless — it ships zero styles. You own all CSS via a stable set of BEM class names.

Host state classes

Applied directly to <ngx-intl-phone-input>:

| Class | When applied | |---|---| | ipi-focused | Phone number input has focus | | ipi-disabled | Component is disabled | | ipi-invalid | Input has a value but it is not valid |

Internal element classes

| Selector | Element | |---|---| | .ipi-country-trigger | Dropdown trigger button | | .ipi-flag | Flag <img> inside the trigger (20×15 px) | | .ipi-dial-code | Dial code span inside the trigger (e.g. +55) | | .ipi-dropdown-panel | CDK overlay panel | | .ipi-search-input | Search <input> inside the dropdown | | .ipi-country-list | <ul> of country options | | .ipi-country-option | Individual <li> | | .ipi-country-option--active | Keyboard-focused option | | .ipi-country-option--selected | Currently selected option | | .ipi-country-option__flag | Flag <img> in the list (20×15 px) | | .ipi-country-option__name | Country name in the list | | .ipi-country-option__dial-code | Dial code in the list | | .ipi-phone-input | The number <input> field |

Important: the dropdown panel renders via CDK into document.body, outside the Angular component tree. All ipi-* styles must be in your global stylesheet, not a component-scoped one.

Minimal style example

// styles.scss
@import '@angular/cdk/overlay-prebuilt.css';

ngx-intl-phone-input {
  display: flex;
  align-items: center;
  height: 40px;
  border: 1px solid #ccc;
  border-radius: 6px;
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;

  &.ipi-focused { border-color: #3b82f6; box-shadow: 0 0 0 3px rgb(59 130 246 / 20%); }
  &.ipi-invalid { border-color: #ef4444; box-shadow: 0 0 0 3px rgb(239 68 68 / 15%); }
  &.ipi-disabled { background: #f7f7f7; }
}

.ipi-country-trigger {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0 0.6rem;
  height: 100%;
  border: none;
  border-right: 1px solid #ccc;
  background: #f8f8f8;
  cursor: pointer;
  white-space: nowrap;
  &:focus { outline: none; }

  .ipi-flag { object-fit: cover; border-radius: 2px; }
}

.ipi-phone-input {
  flex: 1;
  height: 100%;
  border: none;
  padding: 0 0.75rem;
  background: transparent;
  &:focus { outline: none; }
}

.ipi-dropdown-panel {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
  width: 280px;
  max-height: 320px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ipi-search-input {
  padding: 0.6rem 0.75rem;
  border: none;
  border-bottom: 1px solid #e2e8f0;
  background: #f8fafc;
  &:focus { outline: none; }
}

.ipi-country-list {
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
  overflow-y: auto;
}

.ipi-country-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.75rem;
  cursor: pointer;

  &:hover,
  &.ipi-country-option--active { background: #eff6ff; }
  &.ipi-country-option--selected { font-weight: 600; }

  &__flag { object-fit: cover; border-radius: 2px; }
}

Why not ngx-intl-tel-input?

Two specific bugs motivated this library:

  • Russia (+7 926 920 9992): The library auto-switched the selected country to Kazakhstan (both share dial code +7), making the number impossible to validate as Russian.
  • Suriname (+597 850 9292): Numbers were never marked valid and the dial code was corrupted.

Our fix: the user's dropdown selection is always the source of truth. The country never auto-switches based on the typed number. libphonenumber-js validates strictly within the chosen country's context.

License

MIT © JoaoHenriqueAlmeida