Dots

Country Select

A searchable country picker with flags, calling codes, a popular group and unavailable countries.

Country of residenceWhere you live and pay taxes.
"use client"

import * as React from "react"

The CountrySelect component picks one country from a searchable list. Use it for country of residence, a transfer destination or a billing address. Put the countries people pick most in a popular group on top, and keep unsupported countries listed but not selectable so people know why they cannot pick them. Pick it over a plain Select when the list is long enough to need search, and use PhoneField instead when you collect a phone number.

Installation

pnpm dlx shadcn@latest add @dots/country-select

Usage

import { CountrySelect, type Country } from "@/components/ui/country-select"
const countries: Country[] = [
  { code: "CA", name: "Canada", dialCode: "+1" },
  { code: "GB", name: "United Kingdom", dialCode: "+44" },
  { code: "US", name: "United States", dialCode: "+1" },
]
 
<CountrySelect
  label="Country of residence"
  countries={countries}
  defaultValue="US"
/>

Composition

CountrySelect builds the whole picker. Use the parts on their own to put the list in a sheet or page:

CountrySelect
├── Label
├── SelectTrigger
│   └── CountryFlag + name or calling code
├── description (Text slot="description")
├── FieldError
└── Autocomplete
    └── SelectPopover
        ├── SelectInput (search)
        └── CountryList
            └── CountryListItem
                └── CountryFlag

Features

  • Search that matches the name, the calling code and the ISO code, ignoring accents and case
  • An optional popular group on top; those countries are not repeated below
  • Unavailable countries shown with a marker but not selectable
  • A trigger that shows the name or the calling code
  • Monochrome flags when you pass them, a round code chip when you do not
  • Label, description and error message, with form name and validation from React Aria Select
  • CountryList exported on its own for sheets and full-page pickers

Pass country codes to popular to list them first, for example from recent transfers or the person's location. Rename the group with popularLabel. Mark countries you do not support yet with isUnavailable.

Send money toBrazil and Mexico are not available yet.
"use client"

import {

Calling codes

Set display="dial-code" to show the calling code on the trigger, and showDialCode to show codes in the list. Use size="sm" for a compact pill.

"use client"

import {

Invalid

Set isInvalid and an errorMessage, or use isRequired with form validation.

Country of residenceChoose the country on your ID.
"use client"

import {

Disabled

Set isDisabled when the country can no longer be changed, and say why in the description.

Country of residenceContact support to change your country.
"use client"

import {

List on its own

CountryList is the same list without the trigger and popover. Wrap it in React Aria Autocomplete with a SearchField to build a picker in a sheet or on its own page.

United States
United Kingdom
Canada
Australia
Brazil, Not available
France
Germany
Japan
Mexico, Not available
Spain
United Arab Emirates
"use client"

import * as React from "react"
const { contains } = useFilter({ sensitivity: "base" })
 
<Autocomplete filter={contains}>
  <SearchField aria-label="Search countries">
    <Input placeholder="Search countries" />
  </SearchField>
  <CountryList
    aria-label="Country of residence"
    countries={countries}
    selectionMode="single"
    selectedKeys={[country]}
    onSelectionChange={onSelectionChange}
  />
</Autocomplete>

Accessibility

CountrySelect is built on React Aria Select and Autocomplete. The trigger, list and search field get their roles, keyboard navigation, typeahead and focus management from React Aria, and the description and error are linked to the field.

  • Pass a visible label. Without one, pass aria-label, for example "Country code" next to a phone number.
  • The search field is named by searchLabel. Change it when you translate the interface.
  • Flags are decorative and hidden from screen readers; the name or code always sits next to them.
  • With display="dial-code" the country name is still read before the calling code.
  • Unavailable countries are disabled options, and the unavailable marker is read after the name.
  • Give a standalone CountryList an aria-label and its search field an aria-label.

API Reference

CountrySelect

Accepts React Aria Select props such as isDisabled, isInvalid, isRequired, name and aria-label, except items, selectionMode and the selection props it replaces.

PropTypeDefaultDescription
countriesCountry[]-Required. Every country to list, in the order to show them.
popularstring[]-Codes shown first under popularLabel. They are not repeated below.
valuestring | null-Selected country code (controlled).
defaultValuestring | null-Selected country code (uncontrolled).
onChange(code: string | null) => void-Called with the new country code.
labelReactNode-Visible label. Without it, pass aria-label.
descriptionReactNode-Help text under the trigger.
errorMessageReactNode | ((validation: ValidationResult) => ReactNode)-Shown when the field is invalid.
display"name" | "dial-code""name"What the closed trigger shows next to the flag.
showDialCodebooleanfalseShow calling codes in the list.
size"default" | "sm""default"The trigger size.
placeholderstring"Select a country"Shown when nothing is selected.
searchLabelstring"Search countries"Accessible name of the search field.
popularLabelstring"Popular"Heading of the popular group.
allLabelstring"All countries"Heading of the rest of the list, shown when there is a popular group.
emptyMessageReactNode"No countries found"Shown when the search matches nothing.
unavailableLabelstring"Not available"Marker next to unavailable countries.
popoverAnchorRefObject<Element | null>-Element the list opens under and takes its width from. Defaults to the trigger.
classNamestring-Additional classes to apply to the root element.
triggerClassNamestring-Additional classes to apply to the trigger.

Country

The shape of each entry in countries.

FieldTypeDescription
codestringRequired. ISO 3166-1 alpha-2 code, e.g. "GB". Used as the key.
namestringRequired. Name in the interface language.
dialCodestringCalling code with the plus, e.g. "+44". Searchable.
phoneMaskstringNational number mask, one # per digit. Used by PhoneField.
flagReactNodeA monochrome flag. Without it the code is shown in a round chip.
isUnavailablebooleanListed but not selectable.

CountryList

The list on its own. Accepts React Aria ListBox props such as aria-label, selectionMode, selectedKeys and onSelectionChange, except items, children and renderEmptyState.

PropTypeDefaultDescription
countriesCountry[]-Required. Every country to list.
popularstring[]-Codes shown first. They are not repeated below.
showDialCodebooleanfalseShow calling codes in the list.
popularLabelstring"Popular"Heading of the popular group.
allLabelstring"All countries"Heading of the rest of the list.
emptyMessageReactNode"No countries found"Shown when nothing matches.
unavailableLabelstring"Not available"Marker next to unavailable countries.
classNamestring-Additional classes to apply to the list.

CountryListItem

One country row. Accepts SelectItem props except id, children and textValue, which come from the country.

PropTypeDefaultDescription
countryCountry-Required. The country to show.
showDialCodebooleanfalseShow the calling code at the end of the row.
unavailableLabelstring"Not available"Marker shown when the country is unavailable.
classNamestring-Additional classes to apply to the row.

CountryFlag

The round mark next to a country: its flag, or its code when there is no flag. Decorative. Accepts Avatar props except children and size.

PropTypeDefaultDescription
countryPick<Country, "code" | "flag">-Required. The country to mark.
size"xs" | "sm""sm"xs is used on the trigger, sm in the list.
classNamestring-Additional classes to apply to the flag.