A fully automated international phone input system that detects the user’s country using multiple geo-IP providers and instantly applies the correct dial code. Built with intl-tel-input, smart fallbacks, and modern UX styling for seamless onboarding.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Phone Input — robust geo detect</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/css/intlTelInput.css">
</head>
<body class="p-4">
<div class="d-flex justify-content-center align-items-center vh-100">
<fieldset class="w-50">
<div class="form-row">
<div class="form-group col-md-6 mx-auto">
<input type="tel" id="phone2" class="form-control" placeholder="Loading...">
</div>
</div>
</fieldset>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>
</body>
</html>
<style>
/* Primary color #c34b9a */
.iti__country-list li:hover,
.iti__country.iti__highlight {
background: #c34b9a !important;
color: #fff !important;
}
.iti input:focus {
border-color: #c34b9a !important;
box-shadow: 0 0 0 0.2rem rgba(195,75,154,0.25) !important;
}
body::-webkit-scrollbar {
display: none;
}
</style>
<script>
(function () {
const input = document.querySelector("#phone2");
// Try multiple geo IP providers in order, return first valid country code
function tryGeoProviders(callback) {
// Helper to validate country codes (2 letters)
function validCode(code) {
return typeof code === "string" && /^[A-Za-z]{2}$/.test(code);
}
// 1) ipapi.co
fetch("https://ipapi.co/json/")
.then(r => r.json())
.then(data => {
const code = (data && data.country_code) ? data.country_code.toUpperCase() : null;
if (validCode(code)) return callback(code);
// else fallthrough to next
return fetch("https://ipwho.is/")
.then(r => r.json())
.then(d2 => {
const code2 = (d2 && d2.country_code) ? d2.country_code.toUpperCase() : null;
if (validCode(code2)) return callback(code2);
// fallback to ipinfo (no token) — may be rate limited
return fetch("https://ipinfo.io/json?token=") // token optional; leave empty if you don't have one
.then(r3 => r3.json())
.then(d3 => {
const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
if (validCode(code3)) return callback(code3);
// fallback to browser locale
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
// last resort: return empty so intl-tel-input will choose default behavior
return callback("");
})
.catch(() => {
// ipinfo failed => try locale
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
});
})
.catch(() => {
// ipwho.is failed, try ipinfo path
fetch("https://ipinfo.io/json?token=")
.then(r3 => r3.json())
.then(d3 => {
const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
if (validCode(code3)) return callback(code3);
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
})
.catch(() => {
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
});
});
})
.catch(() => {
// ipapi failed -> try ipwho.is
fetch("https://ipwho.is/")
.then(r => r.json())
.then(d2 => {
const code2 = (d2 && d2.country_code) ? d2.country_code.toUpperCase() : null;
if (validCode(code2)) return callback(code2);
fetch("https://ipinfo.io/json?token=")
.then(r3 => r3.json())
.then(d3 => {
const code3 = (d3 && d3.country) ? d3.country.toUpperCase() : null;
if (validCode(code3)) return callback(code3);
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
})
.catch(() => {
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
});
})
.catch(() => {
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) return callback(parts[1].toUpperCase());
}
return callback("");
});
});
}
// initialize intl-tel-input using geoIpLookup (so that plugin knows the country from callback)
function initWithCountry(countryCode) {
// intl-tel-input expects lowercase 2-letter ISO (or "auto" with geoIpLookup).
// If countryCode is empty string, we won't pass initialCountry, letting plugin default.
const options = {
separateDialCode: true,
preferredCountries: ["xk", "al", "mk", ], // keep your preferred list
};
if (countryCode && typeof countryCode === "string" && countryCode.length === 2) {
options.initialCountry = countryCode.toLowerCase();
} else {
// if country unknown, let intl-tel-input use its default behaviour (you can set a specific default here)
options.initialCountry = "auto"; // but we'll also provide a geoIpLookup that returns empty => plugin picks its default
options.geoIpLookup = function (cb) { cb(""); };
}
const iti = window.intlTelInput(input, options);
// If plugin has a selected country, set the visible value to the dial code so user sees it
const data = iti.getSelectedCountryData();
if (data && data.dialCode) {
input.value = "+" + data.dialCode;
} else {
// fallback number
input.value = "";
}
}
// Run providers sequence, then init plugin with result
tryGeoProviders(function (countryCode) {
// Some services return 'XK' for Kosovo which is fine (intl-tel-input supports 'xk')
// console.log("Detected countryCode:", countryCode);
if (!countryCode) {
// last resort: try browser locale split (if not already tried inside function)
const nav = (navigator && navigator.language) ? navigator.language : null;
if (nav && nav.includes("-")) {
const parts = nav.split("-");
if (parts.length === 2 && /^[A-Za-z]{2}$/.test(parts[1])) countryCode = parts[1].toUpperCase();
}
}
// If still missing, avoid hardcoding to "de" — use empty to allow plugin default
initWithCountry(countryCode || "");
});
})();
</script>
Your email address will not be published. Required fields are marked *