Phone input (country selector + i18n format)

Country-aware phone input — country selector + locale-aware mask + ISO E.164 normalization on storage. Common need in signup / profile / SMS-verification flows across Koder products. Modeled after Base Web PhoneInput.

Component — Phone input

*tatus* v0.1.0 — Draft.

R1 — Anatomy

  • Country selector (combobox-style; see specs/components/combobox.kmd)

    showing flag glyph + dial-code (e.g. 🇧🇷 +55).

  • Inline divider.
  • Phonenumber input with localeaware mask.
  • Trailing validation icon (✓ on valid E.164, ✗ on invalid).

R2 — Default country

  • Derived from user's locale per specs/i18n/contract.kmd (en-US → US,

    ptBR → BR, esES → ES).

  • Fallback: device locale via navigator.language (web) / Locale.getDefault()

    (Flutter / native) when product locale doesn't map to a country (e.g. enUS is fine; enLatn is not).

  • Manual override always allowed — user picks any country in the selector.

R3 — Format (mask + storage)

  • Display: localeaware mask via libphonenumberequivalent (e.g.

    +55 (11) 91234-5678 for BR; +1 (415) 555-2671 for US).

  • Storage: ISO E.164 (+5511912345678). Always strip mask before

    emitting onChange value.

  • Paste handling: if pasted value parses as E.164 in a different country

    than currently selected, auto-switch the country selector (and announce via live region — see R5).

R4 — Validation

  • Inline: validate per the selected country's libphonenumber rules.
  • Validity states:
    • *mpty* neutral (no icon)
    • *artial / invalid format* muted (no error icon — too noisy mid-typing)
    • *omplete + valid* ✓ icon
    • *omplete + invalid* ✗ icon + error message below
  • Error messages from specs/errors/user-facing-messages.kmd.

R5 — Keyboard navigation

Key Action
Tab into selector Open dropdown / focus current selection
↑ / ↓ in selector Navigate countries
Type letters in selector Jump to country starting with those letters
Tab out of selector Focus phone input
Type / paste in input Mask applies live
Tab out of input Validate + show error if invalid
Esc in selector Close dropdown without changing selection

Liveregion announce on country autoswitch (R3 paste): "Country changed to {country name} based on pasted number."

R6 — Accessibility

  • Both selector and input have associated labels.
  • Selector: role="combobox" per specs/components/combobox.kmd R3.
  • Phone input: <input type="tel" inputmode="tel"> so mobile keyboards

    show the dial pad.

  • Validation icon + error message linked via aria-describedby.

R7 — i18n

  • Country names translated per locale (per specs/i18n/contract.kmd).
  • Mask uses the country's local convention regardless of UI locale

    (a BR phone number is always masked +55 (XX) XXXXX-XXXX even when the UI is in English).

  • Dial codes are universal (no translation needed).

R8 — OUIA

Per specs/testing/ouia-test-hooks.kmd:

  • data-ouia-component-type="PhoneInput"
  • data-ouia-component-id="<input-id>"
  • data-ouia-safe="true" always (input doesn't have async ready states

    beyond the country list load).

Não-escopo

  • SMS verification flow (consumer concern; phone-input emits valid

    E.164 only, downstream handles the OTP loop).

  • libphonenumber library binding (impl detail — Google libphonenumber-js

    for web, libphonenumber-dart for Flutter).

  • Phone-extension support ("ext. 1234") — out of v0; add if a product

    needs it.

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/phone-input.kmd