# Country Select

> A searchable country picker with flags, localized names, optional dial codes, a suggested group and single or multiple selection.

- Type: React component (Inputs)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add country-select`
- Docs: https://wingo-ui.com/components/country-select

## Interaction

Click or tap the field and the list of about 245 countries opens, each row with its flag, its name in your locale and, with showDialCode, its calling code. Your own country (from the locale, or the priority codes you pass) sits on top under "Suggested". A search row on top filters as you type: by name in any language you search in ("deutschland" or "germany"), by ISO code ("US", "GB"), by dial code ("+40") or by a common short name ("USA", "UK", "Holland"), and accents never matter ("cote" finds Côte d'Ivoire, "turkiye" finds Türkiye), with the matching letters in bold. Keyboard: while closed, typing a letter opens the list with that letter in the search, Enter, Space or ArrowDown open it on the chosen country; while open, ArrowDown and ArrowUp move, PageUp and PageDown jump, Enter picks, Escape closes and focus returns to the field. With multiple, each pick adds a chip with its flag (an x on the chip removes it) and the list stays open; maxSelected caps the count. With clearable an x clears the value. On a phone the field opens a bottom sheet with the search pinned under the title and 48px rows; with multiple a Done button closes it. With name, a hidden native select (one hidden input per country with multiple) submits ISO codes and lets browser autofill fill the country.

## Installation

```bash
npx wingo-ui@latest add country-select
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/country-select` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/country-select.tsx`

npm dependencies: `npm i libphonenumber-js`

Wingo UI dependencies (installed automatically):

- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Multi Select](https://wingo-ui.com/components/multi-select) ([Markdown](https://wingo-ui.com/components/multi-select.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useState } from "react"
import { CountrySelect } from "@/components/ui/country-select"

export function ShippingCountry() {
  const [country, setCountry] = useState("US")
  const [markets, setMarkets] = useState<string[]>(["US", "CA"])
  return (
    <div className="flex flex-col gap-4">
      <CountrySelect
        label="Country"
        description="Where we ship your order."
        value={country}
        onValueChange={setCountry}
        priority={["US", "CA", "MX"]}
        required
        name="country"
      />
      <CountrySelect
        multiple
        label="Markets"
        value={markets}
        onValueChange={setMarkets}
        showDialCode
        maxSelected={5}
        clearable
      />
    </div>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `ReactNode` | `""` | The label above the field; plain text also titles the phone sheet. |
| `description?` | `ReactNode` | `""` | Helper text under the field, linked with aria-describedby. |
| `error?` | `ReactNode` | `""` | Replaces the description with an error and marks the field invalid. |
| `required?` | `boolean` | `false` | Adds the asterisk to the label and blocks an empty native form submit. |
| `multiple?` | `boolean` | `false` | Pick several countries: value becomes a string[] of codes, shown as chips with flags. |
| `value?` | `string \| string[]` |  | The chosen ISO 3166-1 alpha-2 code ("US"), or codes with multiple, when you control it. "" is nothing. Lowercase codes are read as uppercase. |
| `defaultValue?` | `string \| string[]` | `""` | The starting value while uncontrolled ([] with multiple). A form reset returns to it. |
| `onValueChange?` | `(value: string) => void \| (value: string[]) => void` |  | Called with the new code (or the list of codes with multiple) after a pick, a chip removal, a clear or browser autofill. |
| `showFlag?` | `boolean` | `true` | The emoji flag in each row, in the field and in chips. Windows has no flag emoji, so it shows a small code chip there. |
| `showDialCode?` | `boolean` | `false` | The calling code ("+1") at the end of each row and after the chosen name. Dial codes are searchable either way. |
| `priority?` | `string[]` |  | Codes listed first under labels.suggested, in this order. Default: the locale's country ("en-US" suggests US). [] removes the group. |
| `countries?` | `string[]` |  | The codes to offer, e.g. the countries you ship to. Default: every country libphonenumber-js knows (about 245). |
| `exclude?` | `string[]` |  | Codes to leave out of the list. |
| `locale?` | `string` | `""` | The language of the names and their sort order, through Intl.DisplayNames ("de-DE" lists Deutschland). Default: the UIProvider's locale, else the browser's. English names stay searchable in other locales. |
| `renderFlag?` | `(code: CountryCode) => ReactNode` |  | Your own flag for a code (an svg flag set). Default: the CountryFlag emoji. |
| `placeholder?` | `string` | `""` | Shown while nothing is chosen. Default "Select a country", or "Select countries" with multiple. |
| `labels?` | `Partial<CountrySelectLabels>` |  | Strings: search ("Search by name or code"), empty ("No country found"), suggested ("Suggested"), allCountries ("All countries"), plus the multi-select strings (selectAll, clearAll, done, selected, limit, more, remove) and the screen reader result count. |
| `search?` | `"auto" \| "always" \| "never"` | `"auto"` | The search row. auto shows it above 8 countries and, on touch, waits for a tap so the keyboard does not cover the list; always focuses it on open everywhere. |
| `clearable?` | `boolean` | `false` | An x in the field while a value is set; it clears the value (all countries with multiple). |
| `clearLabel?` | `string` | `"Clear"` | The accessible name of the clear button. |
| `highlightMatches?` | `boolean` | `true` | Bold letters where a search matches a country name. |
| `leftIcon?` | `ReactNode` |  | An icon at the start of the field: while nothing is chosen (or with showFlag off) in single mode, always in front of the chips with multiple. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | 32, 40 (44 on touch) or 48px, like every field and button. |
| `variant?` | `"soft" \| "outline" \| "underline" \| "ghost"` | `"soft"` | The field box look. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | The corner radius, scaled per size like the button. |
| `invalid?` | `boolean` | `false` | The invalid look without a message. |
| `disabled?` | `boolean` | `false` | Dims the field and blocks it. |
| `readOnly?` | `boolean` | `false` | Shows the country without letting anyone change it. |
| `display?` | `"chips" \| "count" \| "text"` | `"chips"` | With multiple: chips with flags, "3 selected", or the names joined. |
| `maxSelected?` | `number` | `0` | With multiple: the most countries someone can pick (0 is no limit). |
| `maxVisibleChips?` | `number` | `0` | With multiple: chips shown before "+N" (0 fits as many as one line holds). |
| `wrap?` | `boolean` | `false` | With multiple: chips wrap onto more lines and the field grows. |
| `closeOnSelect?` | `boolean` | `false` | With multiple: close the desktop list after each pick (the phone sheet waits for Done). |
| `name?` | `string` | `""` | Renders a hidden native select (hidden inputs with multiple) so forms submit the codes. |
| `autoComplete?` | `string` | `"country"` | The hidden select's autocomplete token, so browser autofill picks the country from the saved address. |
| `id?` | `string` |  | The trigger's id; the label points to it. |
| `fieldProps?` | `Partial<FieldProps>` |  | Extra props for the Field wrapper (orientation, labelAction...). |
| `defaultOpen?` | `boolean` | `false` | Starts with the list open. |
| `open?` | `boolean` |  | Controls the list's open state. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called when the list opens or closes. |
| `responsive?` | `boolean` | `true` | A bottom sheet below 768px instead of the popover. |
| `sheetTitle?` | `string` | `""` | The phone sheet title. Default: the label text, else "Country" ("Countries" with multiple). |
| `side?` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Where the popover opens; it flips when there is no room. |
| `align?` | `"start" \| "center" \| "end"` | `"start"` | How the popover lines up with the field. |
| `maxHeight?` | `number` | `360` | The popover's tallest height in px (it also stops at the screen edge). |
| `virtualThreshold?` | `number` | `100` | Above this many rows only the rows in view render, so the full list scrolls smoothly. |
| `container?` | `HTMLElement \| null` |  | Where the popover and sheet portal to. Default: document.body. |
| `classNames?` | `Partial<Record<"field" \| "label" \| "description" \| "error" \| "counter" \| "box" \| "trigger" \| "value" \| "placeholder" \| "icon" \| "chevron" \| "clear" \| "content" \| "search" \| "list" \| "item" \| "group" \| "empty" \| "chips" \| "chip" \| "overflow" \| "summary" \| "footer" \| "flag" \| "dialCode", string>>` |  | Extra classes for the inner parts: the Select (or MultiSelect) slots plus flag and dialCode. |
| `className?` | `string` |  | Merged onto the Field root when there is a label, description or error, otherwise onto the box. |
| `ref?` | `RefObject<HTMLButtonElement \| null> or (node) => void` |  | The trigger button; other button props (onBlur, aria-*, data-*, form) land there too. |

## Parts

### CountryFlag (component)

The emoji flag for a code, or a small code chip on Windows (which has no flag emoji). Use it in your own rows and summaries. Takes span props.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `code` | `string` |  | An ISO alpha-2 code, "US". |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | 15, 17 or 19px. |
| `fallback?` | `boolean` |  | Force the code chip (true) or the emoji (false). Default: the chip on Windows only. |

### getCountryOptions (function)

The list the component uses: every country with its localized name, dial code, emoji flag and search keywords, sorted by name in the locale. Reuse it for a table, a filter or a server-side list.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `countries?` | `readonly string[]` |  | The codes to describe. Default: every country. |
| `exclude?` | `readonly string[]` |  | Codes to leave out. |
| `locale?` | `string` | `"en-US"` | The language of the names and the sort order. |
| `returns?` | `CountryOption[]` |  | One entry per country. |

### getCountryName (function)

A country's name in a locale through Intl.DisplayNames: getCountryName("DE", "de-DE") gives "Deutschland". Unknown codes come back as the code.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `code` | `string` |  | An ISO alpha-2 code. |
| `locale?` | `string` | `"en-US"` | The language of the name. |

### countryFromLocale (function)

The country a locale points to, with likely subtags: "en-US" gives US, "ro" gives RO, "" when there is none. The default priority uses it.

### countryFlag (function)

The emoji flag as a string from the two regional indicator letters: countryFlag("JP") gives 🇯🇵.

### isCountryCode (function)

True for a supported ISO alpha-2 code, in any case.

### CountryOption (type)

{ code, name, dialCode, flag, keywords }: one country as getCountryOptions returns it.

### GetCountryOptionsParams (type)

The parameters of getCountryOptions.

### CountryCode (type)

The ISO alpha-2 code union from libphonenumber-js.

### CountrySelectLabels (type)

{ search, empty, suggested, allCountries } plus the optional MultiSelect labels.

### CountryFlagProps (type)

The props of CountryFlag.

### CountrySelectProps (type)

The props of CountrySelect: single (value: string) or multiple (multiple: true, value: string[]).

### CountrySelectSlot (type)

The keys of classNames.

### CountrySelectSize (type)

"sm" | "md" | "lg".

### CountrySelectVariant (type)

"soft" | "outline" | "underline" | "ghost".

### CountrySelectRadius (type)

"none" | "sm" | "md" | "lg" | "full".

### CountrySelectSide (type)

"top" | "right" | "bottom" | "left".

### CountrySelectAlign (type)

"start" | "center" | "end".

## Examples

- **With dial codes**: showDialCode adds the calling code to each row and after the chosen name; “+44” finds the UK.
- **Several countries**: multiple shows chips with flags, maxSelected caps the count and clearable empties it.
- **A short list**: countries limits the choice, priority={[]} drops the suggested group and the search hides below 9 rows.
- **Localized names**: locale renders and sorts names through Intl.DisplayNames; English names still match a search.
- **Sizes, variants and radii**: sm, md and lg line up with every field and button.
- **In a checkout form**: required, a validation error and a hidden native select that browser autofill can fill.

## See also

- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Multi Select](https://wingo-ui.com/components/multi-select) ([Markdown](https://wingo-ui.com/components/multi-select.md))
- [Phone Input](https://wingo-ui.com/components/phone-input) ([Markdown](https://wingo-ui.com/components/phone-input.md))
- [Timezone Select](https://wingo-ui.com/components/timezone-select) ([Markdown](https://wingo-ui.com/components/timezone-select.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))

---

Canonical: https://wingo-ui.com/components/country-select
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
