One formatter for every number, US and international alike

Fixkamo-internal
Shipped
August 27, 2026 at 8:43 PM UTC
Author
kamo
Commit
d5f44ac

The formatter every phone surface shared kept the LAST TEN DIGITS of whatever it was handed and dressed them as a NANP number, so "+44 20 7946 0958" rendered as "(207) 946-0958" — a different number, in a different country, that a person could read off the screen and dial. `CreateLeadClient` had its own copy that was worse: it truncated at ten digits, so an international number typed into a lead was mutilated on entry and the original was gone. `app/lib/phone.ts` now formats through libphonenumber-js: display, as-you-type entry, E.164 for the wire, and a length check for validation. Comparison stays hand-rolled — a last-ten-digit match is all the reverse lookup needs, and it has to keep working on the junk a phone system produces (four-digit extensions, feature codes, an empty caller ID on a Local channel leg). That property is still pinned: anything that is not a whole number comes back untouched. Entry goes through `PhoneField` (MUI) or `BarePhoneInput` (for the surfaces that style their own inputs), both over `usePhoneInput`, which exists for the two things that make an as-you-type field usable rather than infuriating: the caret stays with the digit just typed instead of being flung to the end by the reformat, and backspacing over a separator deletes a digit — naively it deletes nothing, because the formatter puts the separator straight back. Forms that already collect a country pass it through, so a UK org's own number is grouped the way the UK groups it rather than against a US template. Gate on `isPossible()`, not `isValid()`: the min metadata rejects reserved exchanges, and gating on validity would stop formatting every 555 number, which is most of a demo org. Also fixed along the way: the "international" field-mapping transform emitted '+' + digits, a plus glued to a national number, which is some other country's number; and the SMS dialpad counted characters rather than digits, so a seven-digit number dressed up as "(949) 374-7" passed its ten-character gate. Deliberately NOT formatted: the phone-instance config fields and the VoIP outbound SMS number. Those are provider configuration and want raw E.164.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing