Phone number toolkit for JavaScript and TypeScript.

Parsing, validation, formatting, and a headless input controller. Zero dependencies.

100% geographic parity, verified against Google libphonenumberAbout 4x faster parsing, measured in CI

Phone inputvalid
馃寪
RegionNumber typeE.164Error

Get started

npm install @telixon/core

One await at startup loads the engine into memory. The rest of the API is synchronous.

quickstart.ts
import { ensureEngineReady, parsePhoneNumber } from '@telixon/core';
await ensureEngineReady();
const number = parsePhoneNumber('+1 (415) 555-0132');
number.isValid(); // true
number.getRegion(); // 'US'
number.getNumberType(); // 'FIXED_LINE_OR_MOBILE'
number.formatE164(); // '+14155550132'

Verified against Google

The conformance suite loads Google libphonenumber's own source at the exact commit the engine was compiled from, so there is no version drift between the library and its reference.

On every push to main and every pull request, CI replays the whole conformance corpus against Google's source, across every supported region, and fails the build on any divergence. Once a week, an exhaustive enumeration re-proves the result across every input the engine can distinguish, at every length. The input controllers go through the same gate: every input is typed character by character, and the controller's resolved number must equal the parser's.

Explore the results on the live dashboard.

1.8B+
inputs in the weekly exhaustive proof
Regions245Methods compared12Allowlisted divergences0

Parity is measured over geographic regions. Non-geographic ranges (Google's region 001, for example +800) are out of scope.

Compiled Engine

Telixon compiles Google libphonenumber's metadata and rules into a deterministic finite automaton (DFA), stored as compact binary tables. Most libraries interpret those rules at runtime, on every parse. Telixon does that work once, ahead of time.

Parsing a number is a walk over the DFA, one digit per step. The state the walk ends on already holds the region, the number type, whether the number is valid, and how to format it.

The engine is lazy by design. Its tables stay out of your initial bundle until you call ensureEngineReady(), which loads and decodes them off the main thread. You choose the moment; nothing loads at import time.

One digit, one step

USFixed line or mobileValid
119 KB
the whole world's numbering rules, compiled and compressed
<1%
of one display frame per keystroke, at the 99th percentile, even at 360 Hz
Every PR
performance-gated, so a regression cannot merge quietly

Measured by the benchmark suite in the repository. Full, dated runs on the live benchmarks.

Input Controller

A complete API for a phone-number field. It handles every action a real input has: insert, delete forward or backward, replace a selection, undo, redo. The caret and the format stay correct on every keystroke.

Hard case, Argentina mobile

In Argentina, a mobile number carries two dialing prefixes that aren't part of the number itself: a leading 0 and a mobile 15. So Telixon runs a full round-trip on every keystroke:

  1. Strips the 0 and the 15, adds the mobile 9, and gets the canonical national number.
  2. Determines the region, number type, and format.
  3. Drops the added 9, restores the 0 and the 15, and applies the resolved format.

The caret survives the whole strip-and-restore round-trip.

See how Argentine mobile numbers are structured.

馃嚘馃嚪
E.164+5491123456789

Wire it yourself

Each editing method takes the current value and selection, and returns the next value and caret.

import {
createNationalInputController,
} from '@telixon/core';
const controller = createNationalInputController({
defaultRegion: 'AR',
});
controller.insert('011 15-2345-678', '9', 15, 15);
// '011 15-2345-6789', caret 16
controller.deleteBackward('011 15-2345-6789', 16, 16);
// '011 15-2345-678', caret 15
controller.undo(); // '011 15-2345-6789'
controller.redo(); // '011 15-2345-678'

Or let web-sdk do it

createPhoneInput binds the controller to a real <input> and handles beforeinput, paste, drop, word delete, and IME.

import {
createPhoneInput,
} from '@telixon/web-sdk';
const phoneInput = createPhoneInput({
mode: 'national',
input: document.querySelector('input')!,
defaultRegion: 'AR',
});
phoneInput.subscribe(({ validationError }) => {
hint.textContent = validationError?.kind ?? '';
});
phoneInput.getPhoneNumber().formatE164();
// '+5491123456789' for the value above

The full controller API lives in the docs.

Developer experience

What it is like to have Telixon in a codebase.

Structured errors

Validation failures return a typed reason with the data needed to act. Eight variants, one discriminated union, exhaustive in a switch.

partial.getValidationError()
// { kind: 'TOO_SHORT', minLength: 10 }

Closed types

Region codes are a literal union generated from the metadata; number types mirror libphonenumber's. A typo is a compile error.

controller.setRegion('USA')
// compile error: not assignable to RegionCode

Bad input never throws

parsePhoneNumber always returns. Invalid input answers with null and a typed reason, not an exception.

Nothing on import

Importing has no side effects: no fetches, no globals. The engine loads only when you call for it.

One object, every query

Validity, possibility with reason, type, region, and four formats: E.164, international, national, RFC 3966.

Tree-shakeable

ESM, named exports only, sideEffects false. Bundlers drop what you don't use.

Synchronous entry

A separate sync-init entry initializes the engine where async won't do.

Region list

A searchable, locale-aware region list with a stable order, as pure state.

Every intent handled

Twenty beforeinput types plus IME composition: typing, autocorrect, paste, drop, cut, word and line deletion. Anything unrecognized is cancelled; nothing reaches the field the controller did not produce.

Undo and redo built in

Bounded history on every field: the undo and redo shortcuts drive it, and the historyUndo and historyRedo input intents are honored.

Flag emoji

Flags derived arithmetically from Unicode letters, one pure function, no assets.

Real placeholders

Each region's example number, run through the same format masks that format the input.

Packages

Telixon is an ecosystem built around one core, with thin layers you add only where you need them.

@telixon/coreTodayThe engine, the full query API, and the input controller. Runs in Node, browsers, edge runtimes, and workers; one import resolves to the right build for each.@telixon/web-sdkTodayThe DOM layer for the input controller. You bring your own markup.
@telixon/web-anatomyPlannedThe styling contract for the web packages: stable class names, semantic tokens, and state attributes.
@telixon/web-themePlannedA ready-made stylesheet implementing the web-anatomy contract.
@telixon/web-componentsPlannedA drop-in phone input as a standard web component.
@telixon/angularPlannedThe same controller with idiomatic Angular bindings.
@telixon/reactPlannedThe same controller with idiomatic React bindings.
@telixon/vuePlannedThe same controller with idiomatic Vue bindings.

Every layer is optional. Take the raw API and build exactly the behavior you need, or take a ready-made layer.