This code snippet helps you to create a format phone number while typing using javascript. The function initializes a country dropdown menu and sets event listeners to open and close the menu. The function generates a list of countries and their prefixes and appends it to the dropdown menu. When a user selects a country, the function sets the selected country’s prefix, flag, and name in the appropriate fields. If the user clicks outside the dropdown menu or hovers out of the menu for more than 2 seconds, the function closes the menu.
Finally, the function also adds and removes a selected modifier class to the selected country in the dropdown menu. Overall, the code creates a country dropdown menu with event listeners to open, close, and select a country.
How to Create JavaScript Format Phone Number While Typing
First of all, load the following assets into the head tag of your HTML document.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
Create the HTML structure for the format phone as follows:
<!-- Recreation of dribble shot by Diana Palavandishvili https://dribbble.com/shots/15474151-Phone-Number-Input-Field-Exploration --> <div class="pn-select" id="js_pn-select" style="--prefix-length: 2"> <!-- Selected prefix --> <button class="pn-selected-prefix" aria-label="Select phonenumber prefix" id="js_trigger-dropdown" tabindex="1"> <img class="pn-selected-prefix__flag" id="js_selected-flag" src="https://flagpedia.net/data/flags/icon/36x27/nl.png" /> <!-- prettier-ignore --> <svg class="pn-selected-prefix__icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#081626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="6 9 12 15 18 9" /> </svg> </button> <!-- Phone number input --> <div class="pn-input"> <label class="pn-input__label">Phonenumber*</label> <div class="pn-input__container"> <input class="pn-input__prefix" value="+31" type="text" name="phonenumber-prefix" id="js_number-prefix" tabindex="-1" /> <input class="pn-input__phonenumber" id="js_input-phonenumber" type="tel" name="phonenumber" pattern="\d*" value="" placeholder=" " autocomplete="nope" required max="10" tabindex="0" /> <small class="pn-input__error"> This is not a valid phone number </small> </div> </div> <!-- Dropdown --> <div class="pn-dropdown" id="js_dropdown"> <div class="pn-search"> <!-- prettier-ignore --> <svg class="pn-search__icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#103155" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg> <input placeholder="Search for countries" class="pn-search__input search" type="search" id="js_search-input" autocomplete="nope" /> </div> <!-- Country list --> <ul class="pn-list list" id="js_list"></ul> <div class="pn-list-item pn-list-item--no-results" style="display: none" id="js_no-results-found"> No results found </div> </div> </div> <div class="dribble-creds"> Recreation of <a href="https://dribbble.com/shots/15474151-Phone-Number-Input-Field-Exploration" target="_blank">this</a> Dribbble shot </div>
Now, style for the format phone using the following CSS styles:
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap"); html { box-sizing: border-box; height: 100%; font-size: 16px; } *, *:before, *:after { box-sizing: inherit; } body { background: #efefef; height: 100%; display: flex; align-items: center; justify-content: center; font-family: "Inter", sans-serif; font-weight: 400; line-height: 1.5; padding: 0 1em; color: #081627; } input[type=search], input[type=tel], input[type=text] { font-size: 1rem; border: 0; font-family: inherit; outline: none; color: inherit; margin: 0; padding: 0; width: auto; max-width: 100%; } input[type=search]::-webkit-input-placeholder, input[type=tel]::-webkit-input-placeholder, input[type=text]::-webkit-input-placeholder { font-weight: 300; color: #6b7280; } input[type=search]::-moz-placeholder, input[type=tel]::-moz-placeholder, input[type=text]::-moz-placeholder { font-weight: 300; color: #6b7280; } input[type=search]:-ms-input-placeholder, input[type=tel]:-ms-input-placeholder, input[type=text]:-ms-input-placeholder { font-weight: 300; color: #6b7280; } input[type=search]:-moz-placeholder, input[type=tel]:-moz-placeholder, input[type=text]:-moz-placeholder { font-weight: 300; color: #6b7280; } :root { --border-radius: 0.75em; --border-color: #c3c3c3; --border-color-active: #0047a5; --dropdown-border-color: #eaeaec; --dropdown-trigger-background-color: #f3f5f9; --dropdown-trigger-hover-background-color: #e6eaf1; --input-error-color: #ff0000; --input-label-color: #85898f; --input-prefix-color: #656b73; --input-phonenumber-color: #081627; --list-item-hover-background: #f3f5f9; } .pn-select { position: relative; border-width: 1px; border-style: solid; border-color: var(--border-color); display: grid; grid-template-columns: 4.5em 1fr; border-radius: var(--border-radius); box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1); transition: all 0.2s ease-out; max-width: 20em; width: 100%; z-index: 1; } .pn-select:focus, .pn-select:focus-within { border-color: var(--border-color-active); box-shadow: 0 0 2px 0 var(--border-color-active); } .pn-dropdown { background: #ffffff; border-radius: var(--border-radius); border-width: 1px; border-style: solid; border-color: var(--dropdown-border-color); box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.135); opacity: 0; padding: 0 0.5em 0.5em; pointer-events: none; position: absolute; top: 140%; transform-origin: left top; transition: all 0.15s ease-out; width: 100%; visibility: hidden; } .pn-select--open .pn-dropdown { pointer-events: all; transform: none; opacity: 1; top: 120%; visibility: visible; } .pn-search { position: relative; display: flex; border-bottom-width: 1px; border-style: solid; border-color: var(--dropdown-border-color); margin-bottom: 0.5em; } .pn-search svg { display: block; height: 1.25rem; left: 0.5em; pointer-events: none; position: absolute; top: 50%; transform: translateY(-50%); width: 1.25rem; } .pn-search input[type=search] { padding-left: 2.5rem; height: 3rem; width: 100%; } .pn-search input[type=search]::-webkit-search-decoration, .pn-search input[type=search]::-webkit-search-cancel-button, .pn-search input[type=search]::-webkit-search-results-button, .pn-search input[type=search]::-webkit-search-results-decoration { display: none; } .pn-list { margin-right: -0.5em; max-height: 10.5em; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ffffff #ffffff; position: relative; } .pn-list::-webkit-scrollbar { width: 10px; } .pn-list:hover { --scrollbar-background: #ffffff; --thumb-background: #c0c4ca; scrollbar-color: var(--thumb-background) var(--scrollbar-background); } .pn-list:hover::-webkit-scrollbar-track { background: var(--scrollbar-background); } .pn-list:hover::-webkit-scrollbar-thumb { background-color: var(--thumb-background); border-radius: 6px; border: 3px solid var(--scrollbar-background); } .pn-list--no-scroll { margin-right: 0; } .pn-selected-prefix { align-items: center; -webkit-appearance: none; -moz-appearance: none; appearance: none; background: var(--dropdown-trigger-background-color); border-radius: var(--border-radius) 0 0 var(--border-radius); border: 0; cursor: pointer; display: flex; justify-content: center; margin: 0; outline: none; padding: 0; transition: background 0.2s ease-out; } .pn-selected-prefix:hover, .pn-selected-prefix:focus { background: var(--dropdown-trigger-hover-background-color); } .pn-selected-prefix__flag { height: auto; width: 1.25rem; } .pn-selected-prefix__icon { display: block; height: 1.25rem; margin-left: 0.5em; margin-right: -0.25em; transition: all 0.15s ease-out; width: 1.25rem; } .pn-select--open .pn-selected-prefix__icon { transform: rotate(180deg); } .pn-input { background: #ffffff; border-radius: 0 var(--border-radius) var(--border-radius) 0; line-height: 1; overflow: hidden; padding: 0.5em 1em; } .pn-input__container { display: flex; flex-direction: row; } .pn-input__label { color: var(--input-label-color); font-size: 0.7rem; position: relative; top: -0.25em; } .pn-input__error { bottom: 0; color: var(--input-error-color); font-size: 0.785rem; left: 0; opacity: 0; pointer-events: none; position: absolute; transition: all 0.2s ease-out; z-index: -1; } .pn-input input[type=text] { background: transparent; position: absolute; color: var(--input-prefix-color); max-width: 3rem; pointer-events: none; } .pn-input input[type=tel] { color: var(--input-phonenumber-color); padding-left: calc(calc(var(--prefix-length) * 1ch) + 1.5ch); font-weight: 500; } .pn-input input[type=tel]:not(:-moz-placeholder-shown):invalid + .pn-input__error { opacity: 1; transform: translateY(175%); } .pn-input input[type=tel]:not(:-ms-input-placeholder):invalid + .pn-input__error { opacity: 1; transform: translateY(175%); } .pn-input input[type=tel]:not(:placeholder-shown):invalid + .pn-input__error { opacity: 1; transform: translateY(175%); } .pn-list-item { align-items: center; border-radius: 0.5em; display: flex; font-weight: 400; padding: 0.6em 0.75em; transition: background-color 0.2s ease-out; cursor: pointer; outline: none; } .pn-list-item__flag { width: 1.25em; height: auto; margin-right: 1em; display: block; } .pn-list-item__country { margin-right: 0.25em; } .pn-list-item:hover, .pn-list-item:focus { background-color: var(--list-item-hover-background); } .pn-list-item--selected { pointer-events: none; font-weight: 500; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23103155' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-check'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); background-position: right 0.75em top 50%; background-repeat: no-repeat; background-size: 1.25rem; background-position: right 0.75em top 50%; } .pn-list-item--no-results { pointer-events: none; } .dribble-creds { bottom: 2em; font-size: 0.875rem; left: 0; position: fixed; right: 0; text-align: center; } .dribble-creds a { color: #ea4c89; text-decoration: underline; }
Load the following scripts before closing the body tag:
<script src='https://cdnjs.cloudflare.com/ajax/libs/list.js/2.3.1/list.min.js'></script>
Finally, add the following JavaScript function for its functionality:
const selectContainer = document.getElementById("js_pn-select"); const countrySearchInput = document.getElementById("js_search-input"); const noResultListItem = document.getElementById("js_no-results-found"); const dropdownTrigger = document.getElementById("js_trigger-dropdown"); const phoneNumberInput = document.getElementById("js_input-phonenumber"); const dropdownContainer = document.getElementById("js_dropdown"); const selectedPrefix = document.getElementById("js_number-prefix"); const selectedFlag = document.getElementById("js_selected-flag"); const listContainer = document.getElementById("js_list"); let countryList; const init = async countries => { const selectCountry = e => { const { flag, prefix } = e.target.closest("li").dataset; setNewSelected(prefix, flag); closeDropdown(); addSelectedModifier(flag); }; // -------------- Update the 'Selected country flag' to reflect changes const setNewSelected = (prefix, flag) => { selectedFlag.src = `https://flagpedia.net/data/flags/icon/36x27/${flag}.png`; selectedPrefix.value = `+${prefix}`; selectContainer.style.setProperty("--prefix-length", prefix.length); }; // -------------- Removes and adds modifier to selected country const addSelectedModifier = flag => { const previousSelected = document.getElementsByClassName( "pn-list-item--selected")[ 0]; const newSelected = document.querySelectorAll( `.pn-list-item[data-flag=${flag}]`)[ 0]; previousSelected.classList.remove("pn-list-item--selected"); newSelected.classList.add("pn-list-item--selected"); }; // -------------- Close dropdown const closeDropdown = () => { selectContainer.classList.remove("pn-select--open"); listContainer.scrollTop = 0; countrySearchInput.value = ""; countryList.search(); phoneNumberInput.focus(); removeDropdownEvents(); }; // -------------- Open dropdown const openDropdown = () => { selectContainer.classList.add("pn-select--open"); attatchDropdownEvents(); }; // -------------- Dropdown event listeners let countdown; const closeOnMouseLeave = () => { // console.log("countdown activated"); countdown = setTimeout(() => closeDropdown(), 2000); }; const clearTimeOut = () => clearTimeout(countdown); const attatchDropdownEvents = () => { // console.log("Adding event listeners"); dropdownContainer.addEventListener("mouseleave", closeOnMouseLeave); dropdownContainer.addEventListener("mouseenter", clearTimeOut); }; const removeDropdownEvents = () => { // console.log("Removing event listeners and countdown"); clearTimeout(countdown); dropdownContainer.removeEventListener("mouseleave", closeOnMouseLeave); dropdownContainer.removeEventListener("mouseenter", clearTimeOut); }; // -------------- Close when clicked outside the dropdown document.addEventListener("click", e => { if ( e.target !== selectContainer && !selectContainer.contains(e.target) && selectContainer.classList.contains("pn-select--open")) { closeDropdown(); } }); // -------------- Append generated listItems to list element const createList = () => new Promise((resolve, _) => { countries.forEach((country, index, countries) => { const { name, prefix, flag } = country; const element = `<li class="pn-list-item ${ flag === "nl" ? "pn-list-item--selected" : "" } js_pn-list-item" data-flag="${flag}" data-prefix="${prefix}" tabindex="0" role="button" aria-pressed="false"> <img class="pn-list-item__flag" src="https://flagpedia.net/data/flags/icon/36x27/${flag}.png" /> <span class="pn-list-item__country js_country-name">${name}</span> <span class="pn-list-item__prefix js_country-prefix">(+${prefix})</span> </li>`; listContainer.innerHTML += element; if (index === countries.length - 1) { resolve(); } }); }); // -------------- After all the listItems are created we loop over the items to attach the eventListeners const attatchListItemEventListeners = () => new Promise((resolve, _) => { const listItems = [...document.getElementsByClassName("js_pn-list-item")]; listItems.forEach((item, index, listItems) => { item.addEventListener("click", event => { selectCountry(event); }); // Keydown event listener - https://dev.to/tylerjdev/when-role-button-is-not-enough-dac item.addEventListener("keydown", function (e) { const keyD = e.key !== undefined ? e.key : e.keyCode; if ( keyD === "Enter" || keyD === 13 || ["Spacebar", " "].indexOf(keyD) >= 0 || keyD === 32) { e.preventDefault(); this.click(); } }); if (index === listItems.length - 1) { resolve(); } }); }); // -------------- After all the listItems are created we initate list and it's listeners const initiateList = () => { countryList = new List("js_pn-select", { valueNames: ["js_country-name", "js_country-prefix"] }); // Add 'updated' listener for search results countryList.on("updated", list => { if (list.matchingItems.length < 5) listContainer.classList.toggle("pn-list--no-scroll"); noResultListItem.style.display = list.matchingItems.length > 0 ? "none" : "block"; }); }; await createList(); await attatchListItemEventListeners(); initiateList(); dropdownTrigger.addEventListener("click", () => { const isOpen = selectContainer.classList.contains("pn-select--open"); isOpen ? closeDropdown() : openDropdown(); }); }; const countries = [ { name: "Austria", prefix: 43, flag: "at" }, { name: "Belgium", prefix: 32, flag: "be" }, { name: "Bulgaria", prefix: 359, flag: "bg" }, { name: "Croatia", prefix: 385, flag: "hr" }, { name: "Cyprus", prefix: 357, flag: "cy" }, { name: "Czech Republic", prefix: 420, flag: "cz" }, { name: "Denmark", prefix: 45, flag: "dk" }, { name: "Estonia", prefix: 372, flag: "ee" }, { name: "Finland", prefix: 358, flag: "fi" }, { name: "France", prefix: 33, flag: "fr" }, { name: "Germany", prefix: 49, flag: "de" }, { name: "Greece", prefix: 30, flag: "gr" }, { name: "Hungary", prefix: 36, flag: "hu" }, { name: "Iceland", prefix: 354, flag: "is" }, { name: "Republic of Ireland", prefix: 353, flag: "ie" }, { name: "Italy", prefix: 39, flag: "it" }, { name: "Latvia", prefix: 371, flag: "lv" }, { name: "Liechtenstein", prefix: 423, flag: "li" }, { name: "Lithuania", prefix: 370, flag: "lt" }, { name: "Luxembourg", prefix: 352, flag: "lu" }, { name: "Malta", prefix: 356, flag: "mt" }, { name: "Netherlands", prefix: 31, flag: "nl" }, { name: "Norway", prefix: 47, flag: "no" }, { name: "Poland", prefix: 48, flag: "pl" }, { name: "Portugal", prefix: 351, flag: "pt" }, { name: "Romania", prefix: 40, flag: "ro" }, { name: "Slovakia", prefix: 421, flag: "sk" }, { name: "Slovenia", prefix: 386, flag: "si" }, { name: "Spain", prefix: 34, flag: "es" }, { name: "Sweden", prefix: 46, flag: "se" }]; init(countries);
That’s all! hopefully, you have successfully created our project. If you have any questions or suggestions, feel free to comment below.