Skip to content

CountrySelect Pro

Lightweight, zero-dependency vanilla JavaScript country selector

Flags · phone codes · search · custom lists · accessible · Bootstrap 5 or plain CSS · htmx ready

Version 6.2.0 · MIT

Installation

Add the script, then the class country-select to any input or select.

<script src="https://cdn.jsdelivr.net/npm/country-select-pro@6.2.0/dist/country-select.min.js" defer></script>

<input type="text" class="country-select" name="country" value="GR">
npm install country-select-pro

import CountrySelect from 'country-select-pro'; // ES module + TypeScript types
import 'country-select-pro/css';                 // optional: CSS file (strict CSP)

new CountrySelect('#country', { preferredCountries: ['GR', 'CY'] });

Examples

Every demo below is live; the code under it is exactly the HTML that runs. More standalone pages in docs/examples.

Country (ISO code)

The input value is the ISO code (GR). Search ignores accents: try cote or curacao.

Phone code + Bootstrap input group

data-value-type="phone" stores +30. Search by number: type 357.

Preferred countries

Shown first, with a divider. A shared phone code (+1) resolves to the main country.

<select> – progressive enhancement

Your own list in the HTML: works without JavaScript and is crawlable. Option texts are kept.

Custom list – inline JSON

Codes only, or your own names / values. "flag": false for non-country items, data-sort="false" keeps your order.

Custom list – URL / ajax

Any JSON: [...], {countries: [...]}, {"GR": "Greece"}. Missing names / phone codes come from the built-in list.

Localized names

data-lang translates country names with Intl.DisplayNames (any language). Search accepts both: ελλαδα or greece.

Validation

required works with native validation, Bootstrap's .was-validated and .is-invalid.

Events & API

country-change (plus change / input) and methods on the instance.

Plain CSS theme (no framework)

Set --cs-* variables on any parent. Without Bootstrap the defaults are used.

Disabled · readonly · no search · no flags

States follow the input; data-country-search="false", data-flags="false".

Bootstrap dark mode

Inside data-bs-theme="dark" colors follow Bootstrap automatically – no extra CSS.

Not clipped – modals, offcanvas, overflow

The list opens in the browser's top layer (Popover API): this box has overflow: hidden, yet the list is fully visible. The same in Bootstrap modals / offcanvas and <dialog>.

Web Component <country-select>

A real form field: submitted with its name; required, reset, <label for> and disabled <fieldset> work natively. Press Submit with nothing selected.

More: htmx (swap, morph, history) · without any framework · JavaScript API · all examples

Playground

Change the settings – the preview and the code update live.

Preview
value: · country:
HTML
or JavaScript

Options

As data-* attributes, JavaScript options (they win) or global defaults in window.CountrySelectConfig.

AttributeJS optionDefaultDescription
data-value-typevalueTypecodeValue written to the input: code (ISO), phone (+30) or name
data-schemaschema{img} {name}Template of list items: {img} {name} {code} {phone}
data-schema-returnschemaReturn= schemaTemplate of the selected value
data-preferredpreferredCountries–Codes shown first, e.g. GR,CY
data-only-countriesonlyCountries–Show only these codes
data-exclude-countriesexcludeCountries–Hide these codes
data-default-countrydefaultCountry–ISO code selected when the input is empty
data-placeholderplaceholderSelectText when nothing is selected (a <select> uses its empty option)
data-country-searchsearchtrueSearch box
data-search-placeholdersearchPlaceholderSearch...Search box placeholder / label
data-no-resultsnoResultsNot foundEmpty search text
data-country-row-valuesrows5Visible rows
data-dropdown-widthdropdownWidthautoe.g. 300px
data-popoverpopovertrueList in the top layer (Popover API): not clipped by modals / offcanvas / overflow: hidden. false = inside the page
data-langlang–Language of names and texts: el, de … or auto (<html lang>)
data-flagsflagstruefalse = no flags
data-sortsorttruefalse = keep the order of your list
data-countriescountries–Your list: JSON / array / function (may return a Promise)
data-srcdataUrl–URL of your list (JSON)
data-base-urlbaseUrljsDelivrFolder that contains dist/flags/ (self-hosting the flags; the country list is inside the script)
data-flag-pathflagPath{baseUrl}dist/flags/4x3/{code}.svgFlag URL template ({code} = gr, {CODE} = GR)
–fetchOptions–fetch() options for dataUrl
–mergetrueFill missing names / phone codes of your list from the built-in list
window.CountrySelectConfig–Global defaults (any option) + css: false (no built-in CSS), autoInit: false

Events & API

Elements with the class country-select are initialized automatically (also after htmx swaps); new CountrySelect(el | '#selector', options) for manual use.

Methods

CountrySelect.getInstance(el)Instance of an element (or null)
CountrySelect.init(root)Initialize .country-select inside root
cs.setValue(v, {silent})Select by value; sends events unless silent
cs.getValue()Current value
cs.getCountry(){code, name, localName, phone, value} or null
cs.open() · close() · toggle()Dropdown
cs.setCountries(list)New list: array, function, JSON, URL
cs.refresh()Re-read the value after input.value = …
cs.destroy()Remove the widget, restore the input
cs.readyPromise – resolved when the list is loaded

Events (on the input)

input.addEventListener('country-change', (e) => {
    e.detail.value;    // "GR"
    e.detail.country;  // {code, name, localName, phone, value}
});

// change / input are dispatched too:
// <input class="country-select" hx-get="/zip" hx-trigger="change">

Keyboard

↓ ↑ Enter SpaceOpen (closed) · move / select (open)
a letterOpen and search
Home End PgUp PgDnFirst · last · page
Esc · TabClose (a surrounding modal stays open) · leave

Theming

Bootstrap 5: nothing to do (light / dark). Anything else: CSS variables on any parent or :root.

VariableFalls back toUsed for
--cs-bg--bs-body-bg · #fffControl and search background
--cs-color--bs-body-color · #212529Text and arrow
--cs-border-color--bs-border-color · #ced4daBorders
--cs-radius--bs-border-radius · .375remCorners
--cs-font-size1remControl text
--cs-focus-border · --cs-focus-ring#86b7fe · --bs-focus-ring-colorFocus
--cs-hover-bg--bs-tertiary-bgOption hover
--cs-active-bg · --cs-active-color--bs-primary · #fffHighlighted option
--cs-muted--bs-secondary-colorPlaceholder, messages
--cs-search-bg--bs-tertiary-bgSearch area
--cs-dropdown-bg · --cs-dropdown-border-color--cs-bg · --bs-border-color-translucentDropdown
--cs-divider-color--bs-border-colorDivider after preferred
--cs-invalid--bs-form-invalid-border-colorInvalid state
--cs-disabled-bg--bs-secondary-bgDisabled state
--cs-shadow · --cs-z-index– · 2050Dropdown shadow, stacking