Skip to content

I9kPhoneInput ​

Phone number input with a compact country-code picker: a flag-and-code face over a transparent native select that lists every country by name. Registers the number, not the picker, with a wrapping I9kField.

View as MarkdownView source
ts
import { I9kPhoneInput } from '@9klabs/design';

Examples ​

Inside I9kField ​

vue
<I9kField label="WhatsApp number">
  <I9kPhoneInput v-model="phone" v-model:country="country" country-label="Country code" />
</I9kField>

Arabic country names ​

vue
<I9kField label="Ψ±Ω‚Ω… واΨͺΨ³Ψ§Ψ¨">
  <I9kPhoneInput v-model="phone" v-model:country="country" country-label="مفΨͺΨ§Ψ­ Ψ§Ω„Ψ―ΩˆΩ„Ψ©" locale="ar" />
</I9kField>

Error state ​

vue
<I9kField label="WhatsApp number" error="That number doesn't look right.">
  <I9kPhoneInput v-model="phone" v-model:country="country" country-label="Country code" />
</I9kField>

Watch out ​

  • The component only collects the country and the typed number; it does not validate or normalise them.
  • The server renders only the selected country; the named list appears after hydration.
  • Inside an I9kField, do not pass id, aria-invalid, or aria-describedby β€” the field supplies them to the number.
  • Use autocomplete="tel-national", not "tel": "tel" autofills the number with its country code, which the picker already shows.
  • disabled freezes both the number and the country; readonly has no <select> equivalent and freezes only the number.

API ​

Props ​

PropTypeDefaultRequired
modelValuestringβ€”yes
countrystringβ€”yes
countryLabelstringβ€”yes
localestring'en'no
countryNamestringundefinedno
uiSize'sm' | 'md' | 'lg'undefinedno

Emits ​

EventPayload
update:modelValue[value: string]
update:country[country: string]

Agent prompt ​

Paste this into an agent to have it use the component correctly. It is self-contained.

text
Use I9kPhoneInput from @9klabs/design for a phone number that needs a country calling code, inside an I9kField.

import { I9kField, I9kPhoneInput } from '@9klabs/design';

Props:
- modelValue: string (required) β€” the number as typed, v-model target. The component does not parse or validate it; combine it with the country yourself (libphonenumber-js is a good fit).
- country: string (required) β€” ISO 3166-1 alpha-2 code, v-model:country target. Must be a key of I9K_COUNTRY_DIAL_CODES.
- countryLabel: string (required) β€” accessible name of the country select, e.g. "Country code".
- locale?: string β€” language the country names are listed in (default 'en'). Pass the page locale.
- countryName?: string β€” name attribute for the country select.
- uiSize?: 'sm' | 'md' | 'lg' β€” falls back to a wrapping I9kField's size, then to 'md'.

Emits: update:modelValue with the typed string; update:country with the picked code.

Behavior: inside an I9kField, only the number registers with the field, so the field's label, hint and error describe the number; the select is named by countryLabel. Other attributes (name, autocomplete, placeholder) go to the number input, which is type="tel"; class and style go to the wrapper. The control is always dir="ltr" so the code sits before the number in Arabic too. Standalone, give the number an accessible name with aria-label or aria-labelledby.

IMPORTANT: the named country list is rendered only after mount β€” the server renders just the selected code β€” because server and browser ICU name and sort countries differently and would otherwise hydrate out of step. Do not rely on the full option list in server-rendered HTML.

Usage:
<I9kField label="WhatsApp number"><I9kPhoneInput v-model="phone" v-model:country="country" country-label="Country code" name="phone" autocomplete="tel-national" /></I9kField>

Made with love πŸ’š, caffeine, and Claude Code. Built with @9klabs/design itself.