Formclick

How to Validate Phone Numbers in HTML Forms

Phone numbers are messy — different lengths, prefixes and formats around the world. Learn how to collect them reliably with type="tel", sensible patterns and country-aware validation.

FFormclick Team3 min read
Close-up of a smartphone displaying emergency number 112 outdoors.
Photo by cottonbro studio on Pexels
On this page
  1. Step 1: Use type="tel"
  2. Step 2: Decide what you actually need
  3. Step 3: A flexible pattern for international numbers
  4. Step 4: Single-country patterns
  5. Step 5: Ask for the country separately
  6. Step 6: Normalise to E.164 before storing
  7. Step 7: Validate on the server
  8. UX tips
  9. Putting it together

Phone numbers look simple until you collect them from real people. One person types 07700 900123, another +44 7700 900123, another (555) 010-4477, and another adds an extension. Validation that's too strict rejects real customers; validation that's too loose fills your CRM with junk.

Here's a practical approach.

Step 1: Use type="tel"

<label for="phone">Phone number</label>
<input id="phone" name="phone" type="tel" autocomplete="tel">
  • Phones show a numeric keypad with +, * and #.
  • autocomplete="tel" lets browsers fill in the person's number.
  • Unlike type="email", type="tel" doesn't validate any format — on purpose, because formats vary so much worldwide.

Never use type="number" for phone numbers. It drops leading zeros, may show spinner arrows, and rejects +, spaces and dashes.

Step 2: Decide what you actually need

Before writing a pattern, ask:

  • Do you serve one country or many? A single-country form can be stricter.
  • Will you call or text the number? If you'll send SMS, you need it in full international format.
  • Is it required? If you only call occasionally, making it optional increases completions.

Step 3: A flexible pattern for international numbers

This accepts an optional +, then digits with common separators, and requires a sensible length:

<input id="phone" name="phone" type="tel" autocomplete="tel"
       pattern="^\+?[0-9\s\-\(\)\.]{7,20}$"
       title="Enter a phone number, e.g. +44 7700 900123">

It allows +1 (555) 010-4477, 07700 900123 and +91 98765 43210, and rejects letters and obviously short entries. It won't catch every invalid number — no simple pattern can.

Step 4: Single-country patterns

If you only accept one country, you can be precise. Strip separators first and validate the digits. For example, a US number (10 digits, optional +1):

<input id="phone" name="phone" type="tel" autocomplete="tel"
       pattern="^(\+?1[\s\-.]?)?\(?[2-9][0-9]{2}\)?[\s\-.]?[0-9]{3}[\s\-.]?[0-9]{4}$"
       title="Enter a 10-digit US phone number">

Even then, consider an "I'm outside the US" option — customers travel and move.

Step 5: Ask for the country separately

For international forms, a country selector next to the number makes formatting unambiguous and lets you show the right example:

<div class="phone-row">
  <label for="cc" class="sr-only">Country code</label>
  <select id="cc" name="phone_country" autocomplete="tel-country-code">
    <option value="+1">🇺🇸 +1</option>
    <option value="+44">🇬🇧 +44</option>
    <option value="+91" selected>🇮🇳 +91</option>
    <option value="+61">🇦🇺 +61</option>
  </select>

  <label for="phone" class="sr-only">Phone number</label>
  <input id="phone" name="phone" type="tel" autocomplete="tel-national"
         inputmode="tel" placeholder="98765 43210">
</div>

autocomplete="tel-country-code" and tel-national tell the browser to fill the two parts separately.

Step 6: Normalise to E.164 before storing

E.164 is the international standard format: a +, the country code, then the number with no spaces — for example +447700900123. Storing numbers this way makes them work with SMS providers, CRMs and click-to-call.

function toE164(countryCode, national) {
  const digits = national.replace(/\D/g, '').replace(/^0+/, ''); // drop separators and trunk zero
  return countryCode + digits;
}

toE164('+44', '07700 900123'); // "+447700900123"

The "drop the leading zero" rule works for many countries but not all (Italy, for example, keeps it). For real-world accuracy across countries, use a dedicated library such as libphonenumber (available as libphonenumber-js for JavaScript), which knows the rules for every country — validity, length, mobile vs landline and formatting.

import { parsePhoneNumberFromString } from 'libphonenumber-js';

const phone = parsePhoneNumberFromString('07700 900123', 'GB');
phone.isValid();   // true
phone.number;      // "+447700900123"

Step 7: Validate on the server

Run the same check where the data is received. Browser validation can be bypassed, and server-side libraries are just as easy to use. If the number is critical (two-factor login, delivery updates), verify it by sending a one-time code by SMS.

UX tips

  • Show an example in the placeholder or hint text.
  • Accept any separators and clean them up yourself — don't force people to remove spaces.
  • Don't split the number into three boxes — it breaks paste and autofill.
  • Explain why you need it: "So the driver can reach you on delivery day."
  • Make it optional unless you truly need it.

Putting it together

<form action="https://api.formclick.io/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">

  <label for="phone">Phone (optional)</label>
  <input id="phone" name="phone" type="tel" autocomplete="tel"
         pattern="^\+?[0-9\s\-\(\)\.]{7,20}$"
         title="Enter a phone number, e.g. +44 7700 900123"
         aria-describedby="phone-hint">
  <p id="phone-hint" class="hint">Include your country code if you're outside the UK.</p>

  <button type="submit">Request a callback</button>
</form>

Submissions posted to Formclick arrive with the number exactly as typed, so you can normalise it in your CRM or with a webhook workflow. Need a ready-made layout? Try the callback request template.

Found this useful? Share it.

All articles