Skip to content

PhoneInput

A core InputController attached to a DOM <input>. It wires the field’s events, writes each new value and selection back, and emits a PhoneInputState snapshot to subscribers. No emit fires at construction. The initial state goes straight to the DOM; read it with getState. The display modes and filters run live in the input playground.

function createPhoneInput(options: PhoneInputOptions): PhoneInput;

Attaches to an <input type="text"> or <input type="tel">; any other type throws. A second attach to the same element throws. Before the engine is loaded it throws EngineNotReadyError; see Load the engine. destroy releases the element.

const phone = createPhoneInput({
mode: 'national',
defaultRegion: 'US',
input: document.querySelector('input')!,
});
phone.getState().placeholder; // '(201) 555-0123'

A discriminated union on mode, exported as NationalPhoneInputOptions and InternationalPhoneInputOptions. 'national' extends NationalInputControllerConfig; 'international' extends InternationalInputControllerConfig. The adapter adds these fields:

Field Type Meaning
input PhoneInputElement Required. The <input> to attach to, type="text" or type="tel".
regionFilter readonly RegionCode[] | null Region filter applied at construction.
numberTypeFilter readonly NumberType[] | null Number-type filter applied at construction.
placeholderNumberType NumberType The type the placeholder demonstrates; 'MOBILE' by default.

A value already present in the input element seeds the controller when initialValue is omitted, which keeps a server-rendered value or one the browser filled before the widget attached.

type PhoneInputState = {
readonly value: string;
readonly region: RegionCode | null;
readonly selectionStart: number;
readonly selectionEnd: number;
readonly regionFilter: readonly RegionCode[] | null;
readonly numberTypeFilter: readonly NumberType[] | null;
readonly placeholder: string | null;
readonly validationError: ValidationError | null;
};

value, region, and the selection mirror the core InputState. regionFilter and numberTypeFilter are the filters currently applied.

placeholder is the example number for the resolved region, in the field’s own display shape. While the value resolves no region, it falls back to the configured defaultRegion. It is null without a region to fall back to, or when the region’s metadata carries no example for the configured type. A national US field gets '(201) 555-0123'. An international field with the calling code inside gets '1 201-555-0123', with a leading + when plusPrefix shows one.

It lives in the state only. The DOM placeholder attribute stays untouched; you render it yourself.

validationError is getValidationError for the current value. An empty field reports EMPTY.

  • Typing, including IME composition (insertText, insertReplacementText, compositionend)
  • Paste, drop, yank (insertFromPaste, insertFromDrop, insertFromYank): pasted text that holds a whole international number is taken as that number, as insert describes
  • Deletes, including cut and drag-out (deleteContentBackward, deleteContentForward, deleteByCut, deleteByDrag, deleteContent)
  • Word deletes (deleteWordBackward, deleteWordForward): each consumes one digit group, skipping adjacent formatting
  • Line deletes (deleteSoftLineBackward, deleteHardLineBackward, deleteSoftLineForward, deleteHardLineForward, deleteEntireSoftLine, deleteEntireHardLine)
  • Undo and redo: the native historyUndo and historyRedo input types, plus Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z, and Cmd/Ctrl+Y
  • Browser autofill and password managers: a value that arrives with an input event and no beforeinput is read back from the field. A browser fills the number the way the user stored it, usually in national form with the trunk prefix. The field parses that text against its region and, when it is a valid number there, takes it as that number

The widget owns every edit of the value. It cancels each beforeinput it performs and each one it does not recognize. Two types pass untouched, insertLineBreak and insertParagraph, which change nothing in a single-line input and let Enter submit the form. An insert whose text the event does not carry also passes. The input event that follows reads the result back through the controller.

subscribe(listener: PhoneInputListener): () => void;

Subscribes to state changes. Returns an unsubscribe function. PhoneInputListener is (state: PhoneInputState) => void.

getState(): PhoneInputState;

The current state, without subscribing.

setValue(value: string): void;

Replaces the value through the controller’s setValue, writes the result back, then emits. A script that assigns input.value directly fires no event. Push such values through this method.

setRegion(region: RegionCode): void;

Switches the region through the controller’s setRegion, then emits.

setRegionFilter(regions: readonly RegionCode[] | null): void;

Applies the region filter, then emits. An equal filter value is a no-op.

setNumberTypeFilter(numberTypes: readonly NumberType[] | null): void;

Applies the number-type filter, then emits. An equal filter value is a no-op.

undo(): void;

Steps the controller history back, then emits. With nothing to undo, it is a no-op.

redo(): void;

Steps the history forward again, then emits. With nothing to redo, it is a no-op.

canUndo(): boolean;

Whether an undo step exists.

canRedo(): boolean;

Whether a redo step exists.

clearHistory(): void;

Drops the undo and redo history, then emits.

getPhoneNumber(): PhoneNumber;

The current value as a PhoneNumber to query.

phone.setValue('4155550132');
phone.getPhoneNumber().formatE164(); // '+14155550132'
destroy(): void;

Detaches the DOM listeners, releases the element, and clears subscribers. Idempotent. After it, the field behaves as a plain input again.

A RegionPicker created with the phone sends each pick to setRegion and follows the phone’s resolved region. attachRegionPicker wires the DOM. The full field, with a live demo, is in Build the complete field.