Phone input
A phone number field with a country button that shows the flag and dial code. The form sends the full international number.
It formats and joins the number. It does not check that the number is valid for the country.
A country button and a number field
Wrap an .input-group with an input[type=tel] in .phone-input. data-country sets the first country and data-name names the hidden input that holds the number the form sends.
<div class="field" style="max-width: 24rem">
<label for="pi-basic">Phone</label>
<div class="phone-input" data-country="TH" data-name="phone">
<div class="input-group">
<input type="tel" id="pi-basic" placeholder="81 234 5678" />
</div>
</div>
<small>Pick a country, or type + and a dial code.</small>
</div>
A full number in the field picks the country
A value that starts with + and a dial code sets the country and leaves the national part in the field. This works for a value set in the markup, for typing and for paste.
<div class="field" style="max-width: 24rem">
<label for="pi-full">Phone</label>
<div class="phone-input" data-country="US" data-name="phone_full">
<div class="input-group">
<input type="tel" id="pi-full" value="+44 7700 900123" />
</div>
</div>
</div>
The form sends the joined number
Spaces, dashes and brackets are dropped. For countries that use a leading trunk 0 (such as Thailand and the UK) it is dropped too. tsunami:phonechange fires when the country or the number changes.
<form class="stack" data-gap="sm" style="max-width: 24rem" id="pi-form" oninput="document.getElementById('pi-result').textContent = new FormData(this).get('mobile') || '(empty)'">
<div class="field">
<label for="pi-mobile">Mobile</label>
<div class="phone-input" data-country="TH" data-name="mobile">
<div class="input-group">
<input type="tel" id="pi-mobile" value="081-234-5678" />
</div>
</div>
</div>
<pre id="pi-result">+66812345678</pre>
</form>
Invalid, disabled and sizes
aria-invalid="true" and disabled go on the input[type=tel]. data-size goes on the .input-group.
<div class="stack" data-gap="sm" style="max-width: 24rem">
<div class="field">
<label for="pi-invalid">Invalid</label>
<div class="phone-input" data-country="TH" data-name="invalid">
<div class="input-group">
<input type="tel" id="pi-invalid" value="12" aria-invalid="true" aria-describedby="pi-invalid-msg" />
</div>
</div>
<small id="pi-invalid-msg">Enter the full number.</small>
</div>
<div class="field">
<label for="pi-disabled">Disabled</label>
<div class="phone-input" data-country="JP" data-name="disabled">
<div class="input-group">
<input type="tel" id="pi-disabled" value="90 1234 5678" disabled />
</div>
</div>
</div>
<div class="field">
<label for="pi-sm">Small</label>
<div class="phone-input" data-country="DE" data-name="small">
<div class="input-group" data-size="sm">
<input type="tel" id="pi-sm" placeholder="151 2345 6789" />
</div>
</div>
</div>
<div class="field">
<label for="pi-lg">Large</label>
<div class="phone-input" data-country="FR" data-name="large">
<div class="input-group" data-size="lg">
<input type="tel" id="pi-lg" placeholder="6 12 34 56 78" />
</div>
</div>
</div>
</div>
Behavior
- The button opens a list of countries with a search box. Search by name, ISO code or dial code.
- Country names come from
Intl.DisplayNamesin the page language (html lang). The list holds about 240 countries and adds about 8 KB totsunami.min.js. - Typing
+and a dial code switches the country as soon as a digit follows the code. The longest dial code wins, so+1 242is the Bahamas and+1 415is the US. For a number that shares the+1code, paste it whole or pick the country from the list. - The hidden input holds
+, the dial code and the digits of the field, or an empty string while the field is empty. The visible text is never rewritten, except to remove a dial code you typed. - The trunk
0is dropped only for a fixed list of countries where it is always a trunk prefix. For any other country the digits are joined as typed. - It does not validate. It does not check length, area codes or number type, so use your server or a library for that.
- The field has
autocomplete="tel-national"unless you set another value. A form reset puts back the first country and the original text.
API
It starts every .phone-input on the page, also one added later.
Methods
| Method | Description |
|---|---|
tsunami.phoneInput.get(el) | Returns { country, dial, number, value } for a .phone-input or an element inside it. |
tsunami.phoneInput.set(el, v) | Sets the field. v is a string ("+66812345678", or national digits for the current country) or { country, number }. It fires the events below. |
Events
| Event | On | Description |
|---|---|---|
tsunami:phonechange | .phone-input, bubbles | The country or the number changed. detail is { country, dial, number, value }: country is the ISO code ("TH"), dial is "+66", number is the digits sent after it and value is the whole number ("+66812345678", or "" when empty). |
input, change | the hidden input, bubbles | Sent when the country or number changes, so a form or the save bar hears it. Typing also sends the native events from the input[type=tel]. |
Keyboard
| Key | Description |
|---|---|
| Enter, Space, ↓ | On the country button, opens the list and moves focus to the search box. |
| ↓, ↑, Home, End | In the search box, move through the countries. |
| Enter | Picks the highlighted country and returns to the number field. |
| Esc | Closes the list. |
Attributes
| Attribute | On | Description |
|---|---|---|
data-country | .phone-input | ISO 3166 code of the first country, such as TH. Default US. It follows the picked country. |
data-name | .phone-input | Name of the hidden input that holds the international number. Without it nothing is sent. |
disabled | input[type=tel] | Disables the number field, the country button and the hidden input. |
aria-invalid="true" | input[type=tel] | Draws the danger border on the group. |
data-size="sm|lg" | .input-group | Matches the input sizes. |
Accessibility
- The country button is named
Country: Thailand +66and opens a listbox. The search box is a combobox that points to the highlighted option witharia-activedescendant. - Label the
input[type=tel]with alabel. Flags are decorative and hidden from assistive technology.