- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"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
└── CountryFlagFeatures#
- 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
nameand validation from React Aria Select CountryListexported on its own for sheets and full-page pickers
Popular and unavailable#
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.
"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.
"use client"
import {Disabled#
Set isDisabled when the country can no longer be changed, and say why in the description.
"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.
"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, passaria-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
CountryListanaria-labeland its search field anaria-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.
| Prop | Type | Default | Description |
|---|---|---|---|
countries | Country[] | - | Required. Every country to list, in the order to show them. |
popular | string[] | - | Codes shown first under popularLabel. They are not repeated below. |
value | string | null | - | Selected country code (controlled). |
defaultValue | string | null | - | Selected country code (uncontrolled). |
onChange | (code: string | null) => void | - | Called with the new country code. |
label | ReactNode | - | Visible label. Without it, pass aria-label. |
description | ReactNode | - | Help text under the trigger. |
errorMessage | ReactNode | ((validation: ValidationResult) => ReactNode) | - | Shown when the field is invalid. |
display | "name" | "dial-code" | "name" | What the closed trigger shows next to the flag. |
showDialCode | boolean | false | Show calling codes in the list. |
size | "default" | "sm" | "default" | The trigger size. |
placeholder | string | "Select a country" | Shown when nothing is selected. |
searchLabel | string | "Search countries" | Accessible name of the search field. |
popularLabel | string | "Popular" | Heading of the popular group. |
allLabel | string | "All countries" | Heading of the rest of the list, shown when there is a popular group. |
emptyMessage | ReactNode | "No countries found" | Shown when the search matches nothing. |
unavailableLabel | string | "Not available" | Marker next to unavailable countries. |
popoverAnchor | RefObject<Element | null> | - | Element the list opens under and takes its width from. Defaults to the trigger. |
className | string | - | Additional classes to apply to the root element. |
triggerClassName | string | - | Additional classes to apply to the trigger. |
Country#
The shape of each entry in countries.
| Field | Type | Description |
|---|---|---|
code | string | Required. ISO 3166-1 alpha-2 code, e.g. "GB". Used as the key. |
name | string | Required. Name in the interface language. |
dialCode | string | Calling code with the plus, e.g. "+44". Searchable. |
phoneMask | string | National number mask, one # per digit. Used by PhoneField. |
flag | ReactNode | A monochrome flag. Without it the code is shown in a round chip. |
isUnavailable | boolean | Listed 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.
| Prop | Type | Default | Description |
|---|---|---|---|
countries | Country[] | - | Required. Every country to list. |
popular | string[] | - | Codes shown first. They are not repeated below. |
showDialCode | boolean | false | Show calling codes in the list. |
popularLabel | string | "Popular" | Heading of the popular group. |
allLabel | string | "All countries" | Heading of the rest of the list. |
emptyMessage | ReactNode | "No countries found" | Shown when nothing matches. |
unavailableLabel | string | "Not available" | Marker next to unavailable countries. |
className | string | - | Additional classes to apply to the list. |
CountryListItem#
One country row. Accepts SelectItem props except id, children and textValue, which come from the country.
| Prop | Type | Default | Description |
|---|---|---|---|
country | Country | - | Required. The country to show. |
showDialCode | boolean | false | Show the calling code at the end of the row. |
unavailableLabel | string | "Not available" | Marker shown when the country is unavailable. |
className | string | - | 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.
| Prop | Type | Default | Description |
|---|---|---|---|
country | Pick<Country, "code" | "flag"> | - | Required. The country to mark. |
size | "xs" | "sm" | "sm" | xs is used on the trigger, sm in the list. |
className | string | - | Additional classes to apply to the flag. |