r/javascript • u/myroslavmartsin • 16h ago
Headless phone input controller with no baked-in UI to fight
https://github.com/martsinlabs/telixonHave you ever been on a project with a polished UI library, animated inputs, a specific look, and then had to add a phone number field? A phone input is not just validation. Under the hood it is real work: formatting the number as you type, keeping the caret in place, and handling events like paste and undo. The ready-made widgets handle that, but each ships its own markup and styling. Matching one to a specific, opinionated design system does not always work.
That is exactly why I built a headless input controller. You pass your styled input into a function, whatever it looks like, and it handles the rest: formatting, validation, the input events, and the caret.
import { createPhoneInput } from '@telixon/web-sdk';
const phone = createPhoneInput({
mode: 'international',
defaultRegion: 'US',
display: { callingCodeInInput: true, plusPrefix: 'erasable' },
input: document.querySelector('input'),
});
phone.subscribe((state) => {
// state.value, state.region, state.validationError (a typed reason)
});
That one call covers the actions people actually do: paste (formatted numbers too, and it dedupes a repeated country code), drag/drop, cut, word/line delete, IME, autofill, and undo/redo. On every keystroke the state carries the fully parsed, validated number, with a typed reason when it is not valid: too short by N, a calling code that cannot exist, and so on.
Under it is a headless controller you can drive directly, with no DOM: insert, deleteBackward, deleteForward, setValue, undo, redo, region/type filters, getPhoneNumber. One controller does national, international with the calling code in or out of the field, and your own country picker. Formatting and validation come from Google's libphonenumber metadata.
Playground to try it live: telixon.dev/playground/input