Country Select
A button-triggered searchable country popover returning the ISO 3166-1 alpha-2 code.
- Status
- Since
0.1.0- Accessibility pattern
- APG combobox (dialog popup)
Last updated
Install
Add Country Select from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/country-selectThe same command installs the registry items it composes: @vegastack/searchable-select, @vegastack/geo-data.
Usage
import { CountrySelect } from "@/components/ui/country-select";
const [country, setCountry] = useState<string>();
<CountrySelect value={country} onValueChange={setCountry} />;CountrySelect is controlled: pass the selected country's ISO 3166-1 alpha-2 code as value and
update it from onValueChange. The component is a single composite — there are no subcomponents to
compose.
Examples
Anatomy
CountrySelect is a thin, data-fed wrapper over
SearchableSelect: the dataset goes in, the ISO code comes
out, and every structural decision — the Select-shaped trigger, the search field inside the panel,
the check on the selected row, the clear control — belongs to that preset.
<CountrySelect value={code} onValueChange={setCode} />
// ↓ renders, internally:
// <SearchableSelect
// items={countries} ← the geo-data COUNTRIES array by default
// value={selected} onValueChange={…} ← the ONE selection path
// renderItem={…} ← flag + name per row
// searchLabel="Search countries" ← the in-panel search field
// />The trigger is w-full, like every other form control — constrain it with a parent
(<div className="max-w-xs">), not with a width of its own; the panel matches the trigger's width.
Country selection
A flag + name list with type-to-filter search inside a popup; the value is the ISO 3166-1 alpha-2 code.
Selected & disabled
With a value set, the trigger shows the country's flag + name and the matching row carries a check;
disabled renders an inert trigger that won't open.
Empty
Nothing selected yet, so the trigger shows a muted placeholder. Open it and type to filter — when no country matches the search, the list shows the empty-results message "No country found."
Custom dataset
Pass a countries array to restrict or extend the offered list (it defaults to the full
COUNTRIES set from geo-data). The selected value is still the ISO code.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible name for the trigger. Defaults to the selected country name or the placeholder. |
className | string | — | Additional class names merged onto the trigger button. |
clearable | boolean | false | Shows a clear control on the trigger while a country is selected. |
countries | Country[] | COUNTRIES | Override the country list. |
disabled | boolean | false | Disable the control. |
id | string | — | id forwarded to the trigger for label association. |
onValueChange | ((code: string) => void) | — | Fired with the selected ISO code when the user picks a country, or "" when it is cleared. |
placeholder | string | 'Select country' | Placeholder shown on the trigger when nothing is selected. |
ref | React.Ref<HTMLButtonElement> | — | Ref forwarded to the trigger button — the component's focusable root (the popover panel is portaled, so the trigger is the stable host element to focus/measure). |
value | string | — | The selected country's ISO 3166-1 alpha-2 code (controlled). |
Data attributes and CSS variables on CountrySelect
| Attribute | Values |
|---|---|
data-slot | "country-select" |
Data API
The dataset is its own registry item, geo-data, installed automatically as a dependency of this
component and shared with RegionSelect — install both and the ISO
data lands in your tree once. Import from your lib alias, not from the component.
Country
| Prop | Type | Default | Description |
|---|---|---|---|
code* | string | — | ISO 3166-1 alpha-2 code, stored as the selected value (e.g. "US"). |
flag* | string | — | The flag emoji (regional indicator pair). |
name* | string | — | The human-readable country name shown in the list (e.g. "United States"). |
COUNTRIES
| Prop | Type | Default | Description |
|---|---|---|---|
COUNTRIES | Country[] | — | The full dataset — 198 ISO 3166-1 alpha-2 entries (code + English name + flag emoji), covering all regions. The default value of the countries prop. Extend it by editing the array; codes are unique. |
getCountryByCode(code)
| Prop | Type | Default | Description |
|---|---|---|---|
getCountryByCode | (code: string | undefined) => Country | undefined | — | Case-insensitive lookup of a country by its ISO 3166-1 alpha-2 code. Returns undefined for an unknown or missing code. |
import { getCountryByCode } from "@/lib/geo-data";
getCountryByCode("us")?.name; // "United States" (case-insensitive)
getCountryByCode("ZZ"); // undefinedAccessibility
- The trigger carries
role="combobox", an explicitaria-label(the selected country name, or the placeholder), andaria-expanded/aria-haspopup="dialog"(Base UI's Select-style combobox pattern — the search input lives inside the popup). - The list is keyboard-navigable (↑/↓/Enter); typing filters by country name or code.
- Flag emoji are
aria-hidden(decorative); the name carries the accessible text. - With
clearable, the clear control is a sibling of the trigger — never a child — so the trigger never becomes an interactive control containing another one.
| Key | Action |
|---|---|
| Enter / Space | Open the popover from the trigger. |
| ↑ / ↓ | Move the highlight between matching countries. |
| Enter | Select the highlighted country and close. |
| Esc | Close without changing the selection. |
| Contract | States tested |
|---|---|
| Behaviour | default, disabled, selected |
| Accessibility | disabled, labeled |
| Visual | default |
Do / Don't
Searchable Select
The Select-shaped Combobox preset — a full-width trigger, an in-panel search field, a check on the selected row, and an optional clear control.
Region Select
A searchable combobox of states/provinces for a country — Combobox-powered filtering, with a free-text fallback for countries with no subdivisions.