Cleave.js Phone Number Input by Country — Free JS Snippet

Cleave.js Phone Number Input by Country · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

What's included

Features

As-you-type formatting from libphonenumber-style rules
Region switcher driven by setPhoneRegionCode()
i18n add-on covers every country in one script
Raw digits via getRawValue() for storage
Field cleared on region change to avoid cross-plan digit confusion
Dial code adornment and per-region placeholder
Numeric keypad on mobile through type="tel" and inputmode
Honest scope note: formatting only, not validation

About this UI Snippet

Cleave.js Phone Number Input by Country — HTML, CSS & JavaScript

Screenshot of the Cleave.js Phone Number Input by Country snippet rendered live

Phone numbers do not share a shape. A US number groups as 555 123 4567, a French mobile as 06 12 34 56 78, a Brazilian one as 11 98765 4321. A single hard-coded mask cannot serve users in more than one country, and a form that guesses wrong shows people a pattern that does not match how they write their own number. Cleave.js takes a different approach: with its phone add-on it runs the number through an as-you-type formatter derived from Google's libphonenumber, so the grouping follows the selected region's actual rules.

Cleave is an older library — it predates the current generation of input-mask tools and has been in maintenance mode for some time — but it is still an excellent, tiny example of how formatting-only input libraries work, and it is still widely deployed. The setup is minimal: load cleave.min.js and the cleave-phone.i18n.js add-on, then create an instance with phone: true and a phoneRegionCode. The i18n add-on is the one to load when you need many regions; the per-country add-ons are smaller if you only need one.

Switching country calls setPhoneRegionCode(), which re-runs the formatter under the new region, and this snippet also clears the value. That is deliberate: digits typed under one numbering plan generally do not make sense under another, and silently reformatting them into a different country's shape produces numbers that look plausible but are wrong. The readout shows the formatted text, the raw digits from getRawValue() — what you would normally store — and a digit count.

One detail worth knowing: the formatter expects the national form of the number, so for the UK, Germany, France, Australia and Japan you type the leading trunk-prefix zero (07911 123456), exactly as people write their own number. Type the digits without it and the formatter cannot recognise the pattern and leaves them ungrouped. The formatting is also deliberately minimal — groups are separated by spaces, with no brackets or dashes.

The most important line in the snippet is the note under the field: this is formatting, not validation. Cleave will happily format any sequence of digits into something phone-shaped. Whether a number is actually possible or valid for a country needs a real metadata library, such as libphonenumber-js or intl-tel-input, which is the natural next snippet if you need that. Store the raw digits plus the country code — not the formatted string — so the number can be re-rendered or validated later.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add libphonenumber-js validation with an inline error message, detect the user's country from the browser locale, or output an E.164 string for an API.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a phone number input with Cleave.js 1.6 and its cleave-phone.i18n.js add-on, loaded from a CDN.

Requirements:
- Create a Cleave instance with phone: true and a phoneRegionCode, and a country <select> that calls setPhoneRegionCode() on change.
- Show the dial code beside the input and update the placeholder for each country.
- Clear the field when the region changes, and provide a button that fills a valid example for the current region with setRawValue().
- Display the formatted value, the raw digits from getRawValue() and a digit count.
- State clearly in the UI that Cleave formats but does not validate.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="cp-card">
  <div class="cp-head">
    <label class="cp-label" for="cpPhone">Phone number</label>
    <select id="cpRegion" aria-label="Country">
      <option value="US">United States (+1)</option>
      <option value="GB">United Kingdom (+44)</option>
      <option value="IN">India (+91)</option>
      <option value="DE">Germany (+49)</option>
      <option value="FR">France (+33)</option>
      <option value="AU">Australia (+61)</option>
      <option value="BR">Brazil (+55)</option>
      <option value="JP">Japan (+81)</option>
    </select>
  </div>
  <div class="cp-field">
    <span class="cp-dial" id="cpDial">+1</span>
    <input id="cpPhone" type="tel" inputmode="tel" autocomplete="tel-national" placeholder="555 123 4567">
  </div>
  <p class="cp-note">Formatting only — Cleave reshapes what you type as you type; it does not verify that a number exists.</p>
  <dl class="cp-read">
    <div><dt>Formatted</dt><dd id="cpFmt">-</dd></div>
    <div><dt>Digits only</dt><dd id="cpRaw">-</dd></div>
    <div><dt>Digit count</dt><dd id="cpLen">0</dd></div>
  </dl>
  <button type="button" class="cp-fill" id="cpFill">Fill with an example</button>
</div>

Step by step

How to Use

  1. 1
    Type a numberType digits into the field. Spaces appear automatically in the right places for the selected country.
  2. 2
    Switch the countryChoose France or Brazil. The dial code, placeholder and grouping change, and the field clears.
  3. 3
    Compare the outputsThe formatted text and the raw digits are shown side by side — store the digits.
  4. 4
    Use the example buttonFill with a sample number for the current region to see its native grouping.
  5. 5
    Delete in the middleBackspace inside the number. The formatting re-flows around your cursor without breaking.

Real-world uses

Common Use Cases

Contact and signup forms
Give users a phone field that matches their local format. For flags, dial-code search and real validation see the intl-tel-input field.
SHOP
Checkout delivery details
Collect a delivery contact number with consistent grouping so couriers can read it.
ADMIN
CRM data entry
Keep a contact database tidy by formatting at the point of entry.
Learning formatting vs validation
A clear demonstration of what a mask can and cannot guarantee about user input.

Got questions?

Frequently Asked Questions

No. It only formats. Use libphonenumber-js or intl-tel-input when you need to know whether a number is possible or valid.

Use cleave-phone.i18n.js for many countries, or a single cleave-phone.{country}.js file when only one region is needed to save bytes.

Store the raw digits (getRawValue) with the country code or an E.164 string, not the formatted display text.

Digits entered under one numbering plan do not carry over safely to another; clearing avoids plausible-looking wrong numbers.

It is in maintenance mode. It works well and is small, but for new projects also consider IMask or a libphonenumber-based library.

Call cleave.setPhoneRegionCode("GB"), then set a value with setRawValue().

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Cleave.js, so in a framework project install it with npm install cleave.js (cleave.js/dist/addons/cleave-phone.i18n.js for phone) instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit, and release it with destroy() when the component unmounts.