country_kit

country_kit

Country data + flag widgets for Flutter. Everything you need to build phone country pickers, country selectors, currency lists — without shipping assets you don't use.

  • 250 countries with name, official name, native name, ISO 3166-1 alpha-2/alpha-3/numeric codes, dial code, currency (code, name, symbol), continent, subregion, capital, and languages
  • Localized names in 40 languages (CLDR data), each an opt-in import so unused languages are tree-shaken away: country.nameIn('fr')Algérie
  • Phone metadata per country: example number, min/max length, and display masks like ### ## ## ## (derived from Google's libphonenumber — including the alternate formats for the 183 countries that group numbers differently by leading digits), plus a TextInputFormatter that applies the right mask as the user types
  • Search that matches how people type: diacritic-insensitive (espana finds España) and ranked by relevance, not alphabetically
  • Flags in 3 shapes — rectangle (3:2), square, circle — as SVGs in optional per-shape asset packages, plus emoji flags at zero asset cost
  • Extra flags: Scotland, Wales, EU, UN, Canary Islands, and more — with names and their own search (SpecialFlags.search('scotland'))

Install

dependencies:
  country_kit: ^1.0.0
  # Add ONLY the flag shapes you render (~1 MB each). None needed for
  # emoji flags.
  country_kit_flags_circle: ^1.0.0
  country_kit_flags_rectangle: ^1.0.0
  country_kit_flags_square: ^1.0.0

Each shape lives in its own asset package so your app never bundles shapes it doesn't use. If a shape package is missing, CountryFlag automatically falls back to the emoji flag.

Country data

import 'package:country_kit/country_kit.dart';

final dz = Countries.byAlpha2('DZ')!;
dz.name;           // Algeria
dz.nativeName;     // الجزائر
dz.dialCode;       // +213
dz.currencyCode;   // DZD
dz.continent;      // Continent.africa
dz.flagEmoji;      // 🇩🇿
dz.phoneMask;      // ### ## ## ##
dz.phoneExample;   // 551234567

Countries.all;               // all countries, name-sorted
Countries.byAlpha3('DZA');
Countries.byDialCode('+1');  // [US, CA, ...] — shared codes return all
Countries.byName('algeria');
Countries.byContinent(Continent.africa);
Countries.search('alger');   // matches names, codes, dial codes
Countries.ofLocale(locale);  // from a Locale's country code
Countries.current;           // from the device locale — great picker default

Localized names

Localized names ship as one generated library per language (40 languages, CLDR data). Import the ones you render and register them once — languages you don't import are tree-shaken out of the binary:

import 'package:country_kit/l10n/fr.dart';

void main() {
  registerCountryNamesFr();
  runApp(const MyApp());
}

dz.nameIn('fr');   // Algérie (English name before registration)

Registered names also feed Countries.search, so French users typing allemagne find Germany. Countries.registerTranslations accepts your own {alpha2: name} maps too — handy for overrides or an unshipped language.

Countries.search is diacritic-insensitive and ranked by relevance, so results arrive in the order a user expects rather than alphabetically.

Countries.search('espana');   // España — accents optional
Countries.search('turkiye');  // Türkiye
Countries.search('US').first; // United States, not a substring match
Countries.search('ind').first;// India, ahead of British Indian Ocean Territory
Countries.search('213');      // Algeria first, then partial dial matches
Countries.search('burma');    // Myanmar — common alternate names resolve too

foldDiacritics is exported if you want the same folding to highlight matches in your own list tiles.

Flags

CountryFlag(country: dz, shape: FlagShape.circle, size: 32)
CountryFlag(country: dz, shape: FlagShape.rectangle,
    borderRadius: BorderRadius.circular(4))
CountryFlag.fromCode('GB-SCT', shape: FlagShape.square) // extra flags
CountryFlag.emoji(dz, size: 32)                         // no assets needed

size is the flag height; rectangles are 1.5 × size wide.

Fallback chain: bundled SVG → parent-territory SVG (e.g. Réunion → France) → emoji flag → placeholder with the code. Emoji rendering is platform dependent (Windows shows letter pairs). Kosovo (XK) has no SVG in the source icon set and renders via the fallback chain.

The placeholder tints itself from the ambient DefaultTextStyle, falling back to the platform brightness, so it stays legible in dark mode. Pass placeholderBuilder (or loadingBuilder, for the frame before assets resolve) to take over completely:

CountryFlag.fromCode('EU',
    placeholderBuilder: (context, code) => MyOwnChip(code))

Special flags (Scotland, EU, UN, …)

The asset packages also bundle 29 non-ISO flags: subdivisions (Scotland, Wales, Sicily, Hawaii, …), organizations (EU, UN) and other regions. Each is a SpecialFlag with an English name, native name, search aliases, and the alpha-2 code of the country it belongs to:

SpecialFlags.search('scotland');       // [SpecialFlag(GB-SCT, Scotland)]
SpecialFlags.search('euskadi');        // Basque Country — native names work
SpecialFlags.byCode('GB-SCT');         // lookup by asset code
SpecialFlags.byCountry('GB');          // Scotland, Wales, Orkney
Countries.byAlpha2('GB')!.specialFlags;// same, from a Country
SpecialFlags.flags;                    // all 29, name-sorted

CountryFlag.fromCode(SpecialFlags.scotland, shape: FlagShape.square)

These are deliberately not countries: a SpecialFlag has no dial code, currency or continent, and Countries.search never returns one — so a phone picker can never surface Scotland. A country selector that wants them opts in by composing the two searches:

final countries = Countries.search(query);
final extras = SpecialFlags.search(query);

To persist a selection, store country.alpha2 (or flag.code) and rehydrate with Countries.byAlpha2 — a Country never needs serializing.

Phone picker in a few lines

PhoneInputFormatter applies the country's mask live as the user types, and handles the fiddly parts: inserting mid-string, backspacing over a separator, and pasting an already-formatted number. 183 countries group numbers differently depending on the leading digits — a German 151 mobile is not grouped like a 157 one — so country.phoneFormats carries every variant and the formatter re-picks the right one on each keystroke.

TextField(
  keyboardType: TextInputType.phone,
  inputFormatters: [PhoneInputFormatter.forCountry(country)],
  decoration: InputDecoration(
    hintText: country.phoneMask,
    prefixIcon: Row(mainAxisSize: MainAxisSize.min, children: [
      CountryFlag(country: country, shape: FlagShape.rectangle, size: 18),
      Text(country.dialCode!),
    ]),
  ),
)

Validate on submit:

switch (country.validatePhoneNumber(controller.text)) {
  case PhoneNumberValidity.empty:    // nothing entered
  case PhoneNumberValidity.tooShort: // below phoneMinLength
  case PhoneNumberValidity.tooLong:  // above phoneMaxLength
  case PhoneNumberValidity.valid:    // right number of digits
}

This is a length check, not a real-number check — it tells you a number is the wrong shape, never that it is a working line. The 13 countries with no phone metadata report valid for any non-empty input.

Formatting without a widget:

country.formatPhoneNumber('551234567');            // '551 23 45 67'
formatNationalNumber('2135551234', mask: '(###) ###-####');

Pasting an international number is not unwound: whether a leading 1 is a country code or a subscriber digit is genuinely ambiguous, so strip the dial code yourself before setting the field.

See example/ for a full searchable country selector and phone picker.

Data & asset credits

Regenerate the dataset with python3 tool/generate_data.py.