React

Phone number masks

A phone mask formats as you type and keeps the number in a separate value: the stored data carries no spaces, dashes or ambiguous prefixes.

Key points

  • Digits are stored without separators; formatting stays in the component.
  • The mask does not validate: a formatted number can still be unreachable.
  • Backspace must remove a digit, not a separator.

A phone mask formats as you type and keeps the full number in a separate value: the stored data must carry no spaces, dashes or ambiguous prefixes.

Formatting is not validation

The mask decides where spaces go; validation decides whether the number can exist at all. A field showing +39 333 123 4567 can still have the wrong length, so the two checks stay apart.

function formatItalianPhone(raw) {
  const digits = raw.replace(/\D/g, "").slice(0, 13);
  if (digits.length <= 3) return digits;
  if (digits.length <= 6) return digits.slice(0, 3) + " " + digits.slice(3);
  return digits.slice(0, 3) + " " + digits.slice(3, 6) + " " + digits.slice(6);
}

The ITU-T E.164 recommendation caps an international number at 15 digits and allows the + prefix: that is the value to store, while formatting stays in the component. It needs checking on at least one Italian number and one foreign number, because the length differs.

Three traps

  • Backspace on the mask: deleting a separator must remove a digit, otherwise the cursor gets stuck.
  • Pasting a full number: the paste event brings +39 and spaces, so the function must normalise before it formats.
  • Fixed width: foreign numbers vary in length, so the field must not be sized for one country only.

One dependency, one licence

npm install react-imask

react-imask, released under the MIT licence, applies the mask on the native input and returns the unformatted value: the hand-written function stays useful when the dependency is too much.

Live records in the catalogue

Three fields with formatting: otp-input, otp-verify and credit-card-form. To browse by category start from 111,985 components.

The limitation

No mask knows which prefixes are in service: a number can match the format and still not exist. The real proof is an SMS or a call, and until it arrives the field stays unconfirmed.

Where to continue

The library choice is in React form libraries; field handling is in react-hook-form with shadcn; the comparison between controlled and uncontrolled fields is in controlled and uncontrolled forms.

Frequently asked questions

Should the number be stored with spaces?

No, store only the digits with the international prefix. Spaces help reading, not identification.

Write the mask or install one?

For a single format a short function is enough. With several countries, react-imask, released under the MIT licence, is the safer base.

Why does the cursor jump to the end?

Because the value is rewritten on every keystroke and the cursor position is lost. Recalculate it or let the library do it.

Should the +39 prefix be visible?

Yes for an international audience, because it states the country. For an Italian-only audience it can sit as fixed text beside the field.

Does the mask check that the number exists?

No, it checks shape only. Existence is proven by an SMS or a call.