Cal.comdesign-system

Phone Input

Phone number input with country combobox.

Use Phone Input anywhere Cal collects a telephone number. It owns country selection and display formatting; the consuming form owns required and validity messages. Wrap it in Field and FieldLabel, matching the label's htmlFor to the input id, so the telephone input has a visible, accessible name. The composite keeps that label on the telephone textbox rather than the adjacent country combobox.

The public booking form is the reference implementation. New phone fields should match its value shape, country-selection behavior, required handling, validation timing, and inline errors unless a documented product requirement calls for different behavior.

Example

Composition matches a real product call site. Copy is paraphrased.

apps/web · public booking form
+1

Canonical composition

Keep the responsibilities separate instead of wrapping product behavior into another input:

This shape follows the public Booker and routing-form fields. A surface using native constraint validation may use inputRef and required; an RHF/Zod form should keep validation in its form schema, following the Forms guide.

Validation

PhoneInput emits the current international value, including incomplete input while someone is typing. It does not decide whether a complete number is acceptable; that policy belongs to the consuming form.

Booking and routing forms pass the emitted value through getPhoneNumberInputValidation, giving them the same three outcomes:

A bare calling code such as +1, or an incomplete supported prefix such as +4, behaves as empty input. An impossible code such as +999, a number that is clearly too short, or a number outside the installed metadata's allocated ranges is invalid.

The globe means that no country has been resolved; it can represent incomplete or invalid input, but also a valid non-geographic number such as +800 1234 5678. Do not use the globe itself as a validation signal.

Do not show an error while someone is entering an incomplete number. Once validation has surfaced an error, clear it as soon as the corrected value satisfies that form's policy.

Country selection

  • defaultCountry is optional. Supply it when an empty field should accept a national number in a detected or product-known country. Public booking and routing forms supply the same IP/locale-derived value.
  • Do not add country detection inside PhoneInput. App surfaces that need it use useDefaultPhoneCountry; the shared composite remains independent of app data fetching.
  • Omit defaultCountry when the existing US fallback is appropriate or when the field always receives an international value. Pass a product-owned fixed country explicitly when it should be the national-number context.
  • A country parsed from a controlled international value takes precedence over defaultCountry, so editing an existing E.164 value does not require deriving a default from it.
  • Entering a recognized international prefix selects the matching country. An unknown or non-geographic prefix leaves the globe selected without opening the picker.
  • When countries share an exact calling code, entering that bare code keeps the selected country when compatible, otherwise it prefers the detected/default country. For example, an Italian default resolves +39 to Italy rather than leaving the selector in the international state.
  • The selector stays compact and flag-only. For resolved countries, a non-editable calling-code addon appears between the selector and the telephone input. The international state uses the globe and omits the addon.
  • Country names follow the application's language setting from the document lang attribute. Country search matches that name, the two-letter ISO code, or calling code. For example, United, GB, and +44 can all find the United Kingdom.
  • Selecting a country returns focus to the telephone input.

Use inputRef when form infrastructure needs the native telephone input for focus management or constraint validation. The ref is populated only while the component is mounted.

API

Call sites

Example URLs

Not exhaustive — a few places you can open locally (e.g. http://localhost:3000) to see this in product.

Source

packages/coss-ui/src/shared/phone-input.tsx