Build a phone field
createPhoneInput binds a plain <input> to a core
input controller; this guide runs it as an international field. The PhoneInput handles the events,
the value, the caret, and the history, while your renderStatus function draws the rest.
Live demo
Section titled “Live demo”Type +14155550132. The field formats it live to +1 415-555-0132 and the status reads
Valid: US +14155550132. Type +442071838750 instead and it switches to the UK
+44 20 7183 8750, since the calling code picks the region.
The rest behaves the way a real field should. The caret holds its digit through a mid-value edit
while the groups reflow. Paste accepts any notation. Deletion runs backward, forward, or a word at
a time. Undo and redo use Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z. A value that falls short reads
TOO_SHORT.
The code
Section titled “The code”The demo and the source are the same three files. The markup is an input and a status line. The
script is one createPhoneInput call in international mode,
where the erasable plus
renders only while the value carries one. renderStatus draws the status from each emitted
state.
<div id="phone-field"> <label for="phone-field-input">Phone number</label> <input id="phone-field-input" type="tel" placeholder="+1 201-555-0123" /> <p class="phone-field-status"></p></div>#phone-field { --surface: #ffffff; --text: #1c1f1e; --muted: #66716d; --border: #c9d1ce; --accent: #26997b; --valid: #1d7a5f; --error: #b3384f;
color: var(--text);}
#phone-field label { display: block; margin-bottom: 0.375rem; font-size: 0.875rem; font-weight: 600;}
#phone-field input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border); border-radius: 0.375rem; background: var(--surface); color: inherit; font-size: 1rem; font-variant-numeric: tabular-nums;}
#phone-field input:focus-visible { border-color: var(--accent); outline: none; box-shadow: 0 0 0 1px var(--accent);}
#phone-field input::placeholder { color: var(--muted);}
#phone-field .phone-field-status { margin: 0.5rem 0 0; min-height: 1.25rem; font-size: 0.875rem; color: var(--muted);}
#phone-field .phone-field-status[data-tone='valid'] { color: var(--valid);}
#phone-field .phone-field-status[data-tone='error'] { color: var(--error);}import { ensureEngineReady } from '@telixon/core';import { createPhoneInput } from '@telixon/web-sdk';
await ensureEngineReady();
const input = document.querySelector('#phone-field input');const status = document.querySelector('#phone-field .phone-field-status');
const phone = createPhoneInput({ mode: 'international', display: { callingCodeInInput: true, plusPrefix: 'erasable' }, input,});
function renderStatus(state) { if (state.value === '') { status.textContent = 'Type a number.'; status.dataset.tone = ''; return; }
if (state.validationError === null) { status.textContent = 'Valid: ' + state.region + ' ' + phone.getPhoneNumber().formatE164(); status.dataset.tone = 'valid'; return; }
status.textContent = state.validationError.kind; status.dataset.tone = 'error';}
phone.subscribe(renderStatus);renderStatus(phone.getState());Covered events lists every input type PhoneInput already
handles, IME composition and drag-and-drop included. The field is one half of the classic widget.
Build a region picker is the other half.