kiltel
v3.0.1
Published
Universal international phone input with flag icons and country code sync.
Maintainers
Readme
Kiltel
Kiltel is a lightweight country picker and phone-number formatter. It has no browser-side phone validator: the browser produces a consistent E.164 value and the server decides whether to accept it.
This avoids the old intl-tel-input validation failure that incorrectly rejected some valid India and Jamaica entries.
How number normalization works
Kiltel synchronizes the following values after every edit (include the optional country_iso hidden input when you need the ISO country in your database):
| Field | Stored value | Jamaica example |
|---|---|---|
| Main phone input | E.164-style full number | +18767425111 |
| country_code | Selected dialing prefix | +1876 |
| contact | National subscriber digits only | 7425111 |
| country_iso | Selected ISO 3166-1 alpha-2 country | JM |
The country prefix is already visible in the main field. Enter only the digits that are not already shown:
- India: with
+91selected, type9876543210, which becomes+919876543210. - Jamaica: with
+1 876selected, type7425111, which becomes+18767425111. Jamaica isJM; it shares the North American numbering plan but is not the United States.
Pasting an international number such as +18767425111 also switches the picker to Jamaica automatically. Do not add a leading 0 for an Indian mobile number or repeat a prefix Kiltel has already supplied.
CDN / plain HTML
Add the stylesheet and script:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/kiltel@3/kiltel.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/kiltel@3/kiltel.min.js"></script>Use this structure for each phone field. The hidden values must be inside the same .kiltel-phone-wrap.
<div class="kiltel-phone-wrap">
<input
type="tel"
class="form-control"
name="phone"
data-kilvish-tel
data-kiltel-init="JM"
placeholder="Enter the remaining phone digits"
required
>
<input type="hidden" name="country_code">
<input type="hidden" name="contact">
<input type="hidden" name="country_iso">
</div>Kiltel initializes on page load. For markup added later, call window.kiltelInit() after it is inserted.
The CDN build also exposes window.kiltel.normalizePhone(value, country). It returns { country, countryCode, nationalNumber, e164, isPlausible }.
Next.js / React
Kiltel manipulates the DOM, so initialize it only from a client component.
Install and add CSS
npm i kiltelImport the CSS once, for example in app/layout.js:
import 'kiltel/kiltel-next.css';App Router client component
'use client';
import { useKiltel } from 'kiltel/kiltel-react';
export default function PhoneField() {
useKiltel();
return (
<div className="kiltel-phone-wrap">
<input
type="tel"
className="form-control"
name="phone"
data-kilvish-tel
data-kiltel-init="IN"
placeholder="Enter the remaining phone digits"
required
/>
<input type="hidden" name="country_code" />
<input type="hidden" name="contact" />
<input type="hidden" name="country_iso" />
</div>
);
}Pages Router or scoped manual initialization
import { useEffect, useRef } from 'react';
import kiltel from 'kiltel/kiltel-next';
import 'kiltel/kiltel-next.css';
export default function PhoneField() {
const wrapRef = useRef(null);
useEffect(() => {
kiltel.init({ scope: wrapRef.current });
return () => kiltel.destroy({ scope: wrapRef.current });
}, []);
return (
<div className="kiltel-phone-wrap" ref={wrapRef}>
<input type="tel" className="form-control" name="phone" data-kilvish-tel data-kiltel-init="JM" />
<input type="hidden" name="country_code" />
<input type="hidden" name="contact" />
<input type="hidden" name="country_iso" />
</div>
);
}Validate on the server
Do not trust hidden form fields and do not add data-kiltel-validation; that legacy attribute is no longer used. Normalize the submitted main input on the server, then persist the normalized values.
kiltel/kiltel-next can safely be imported by a Next.js route handler because normalizePhone does not access the DOM:
import kiltel from 'kiltel/kiltel-next';
export async function POST(request) {
const form = await request.formData();
const phone = kiltel.normalizePhone(
form.get('phone'),
form.get('country_iso')
);
if (!phone?.isPlausible) {
return Response.json({ error: 'Enter a complete phone number.' }, { status: 400 });
}
// Persist these values, not the client-supplied hidden fields.
await saveUser({
phoneE164: phone.e164,
phoneCountry: phone.country,
phoneCountryCode: phone.countryCode,
phoneNationalNumber: phone.nationalNumber
});
return Response.json({ ok: true });
}isPlausible verifies the normalized E.164 shape and length. If your product needs country-specific numbering-plan checks, perform them in that same server route with a maintained server-side telephone library or verification service. No format check can prove that the number is assigned; use SMS or voice verification for that.
Attributes
| Attribute | Purpose |
|---|---|
| data-kilvish-tel | Enables Kiltel on the input. |
| data-kiltel-init | Initial ISO country or dialing prefix, for example IN, JM, or +1876. |
| data-kilvish-only | Comma-separated ISO country allow-list, such as IN,JM,US. |
| data-kilvish-exclude | Comma-separated ISO country block-list. |
| data-kilvish-preferred | Comma-separated ISO countries pinned at the top of the list. |
License
MIT
