Phone input
A phone number field for a country. It groups digits as you type, detects the operator from the prefix, validates the length and gives you the E.164 number.
Preview
Saisissez votre numéro à 9 chiffres, sans l'indicatif du pays.
- value
- ""
- e164
- null
- valid
- false
- issue
- —
Try 6 51 23 45 67, paste +237 655 12 34 56, or use the operator option to require a specific operator.
Installation
npx shadcn@latest add @mpui-kit/phone-inputRequires the @mpui-kit namespace. See Getting started.
Usage
import { PhoneInput } from "@/components/mpui-kit/phone-input"
// Inside <MpuiKitProvider country={cm}>
const [phone, setPhone] = useState("")
<PhoneInput
value={phone}
onChange={(digits, details) => {
setPhone(digits) // "651234567"
console.log(details.e164) // "+237651234567" once the number is valid
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | The national number as digits, e.g. "651234567". An E.164 value such as "+237651234567" is also accepted. Makes the input controlled. |
| defaultValue | string | — | Initial value for an uncontrolled input. |
| onChange | (value: string, details: PhoneValidation) => void | — | Called on every change with the national digits and the validation result. details.e164 is set once the number is valid. |
| country | CountryConfig | MpuiKitProvider's country | Country to use. Throws if neither this nor a provider is set. |
| locale | "fr" | "en" | MpuiKitProvider's locale, then “fr” | Language of the label, hint and built-in errors. |
| label | string | false | “Phone number” | Visible label. Pass false to hide it, and then give aria-label. |
| hint | string | false | length hint | Helper text under the input. Pass false to hide it. |
| error | string | — | Error message from your form library. Overrides the built-in error. |
| operator | string | — | Id of the operator the number must belong to, e.g. "mtn". Only a known, different operator is rejected. A prefix that is not in the data is accepted. |
| requireOperator | boolean | false | Reject numbers whose prefix matches no known operator. |
| operatorLogos | Record<string, ReactNode> | — | Your own logos by operator id, shown in the badge instead of the color dot. |
| name | string | — | Adds a hidden field with this name that holds the E.164 number, so native form posts get it whatever the visible grouping. |
| className | string | — | Styles the outer wrapper. Use classNames.input for the text input. |
| classNames | { label, field, prefix, input, badge, hint, error } | — | Class names for parts of the field. Each part also has a data-slot attribute, such as phone-input-input. |
| ...props | ComponentProps<'input'> | — | Other input props (disabled, required, autoFocus, ref...) go to the text input. |
react-hook-form and zod
Use Controller, not register: the input reports digits through onChange(value, details) rather than a DOM event. createPhoneSchema accepts what people type and outputs the E.164 number, with French or English messages.
import { zodResolver } from "@hookform/resolvers/zod"
import { Controller, useForm } from "react-hook-form"
import { z } from "zod"
import { PhoneInput } from "@/components/mpui-kit/phone-input"
import { cm } from "@/lib/mpui-kit/countries/cm"
import { createPhoneSchema } from "@/lib/mpui-kit/phone-schema"
const schema = z.object({ phone: createPhoneSchema(cm, { locale: "en" }) })
const form = useForm<z.input<typeof schema>, unknown, z.output<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { phone: "" },
})
<form onSubmit={form.handleSubmit((data) => console.log(data.phone))}>
<Controller
control={form.control}
name="phone"
render={({ field, fieldState }) => (
<PhoneInput
ref={field.ref}
name={field.name}
value={field.value}
onChange={(value) => field.onChange(value)}
onBlur={field.onBlur}
error={fieldState.error?.message}
/>
)}
/>
</form>
// data.phone is the E.164 number: "+237651234567"Install the schema helper with npx shadcn@latest add @mpui-kit/phone-schema. It needs zod, which the CLI installs for you.
OperatorBadge
The badge is a color dot and the operator name. MPUI-KIT ships no operator logos or brand assets. Pass your own through logo, or operatorLogos on the input, if you have the right to use them.
| Prop | Type | Default | Description |
|---|---|---|---|
| operator* | OperatorConfig | — | Operator to show. |
| logo | ReactNode | — | Your own logo, shown instead of the color dot. MPUI-KIT ships no logos. |
| ...props | ComponentProps<'span'> | — | Any other span prop. |
Notes
- Prefix data is a hint. Operator prefixes are community-maintained and may be incomplete or out of date, so the badge can be missing or wrong. Never route money by it.
- Accepts local numbers,
+237 …,00237 …and pasted numbers with spaces or dashes. - Errors appear when the field loses focus, not while typing, and are announced with
role="alert". The detected operator is announced politely. - The caret stays where you expect it while digits are regrouped, and Backspace over a space deletes the digit before it.