> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CountrySelect

> A complete country picker and shared parts for prepared choices, stable identifiers, and null clearing.

export const StoryEmbed = ({storyId, title, height = 120}) => <>
    <Tabs>
      <Tab title="Light">
        <iframe title={`${title} (light)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:light`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
      <Tab title="Dark">
        <iframe title={`${title} (dark)`} src={`https://storybook.twenty.com/iframe.html?id=${storyId}&viewMode=story&globals=colorScheme:dark`} width="100%" height={height} loading="lazy" allow="clipboard-write" style={{
  border: 0
}} />
      </Tab>
    </Tabs>
    <a href={`https://storybook.twenty.com/?path=/story/${storyId}`}>
      Open in Storybook
    </a>
  </>;

`CountrySelect` provides a complete country picker with a labeled trigger, decorative flags, search, and a no-country choice. `CountrySelect.Root`, `CountrySelect.Trigger`, and `CountrySelect.Options` expose the same implementation for composition with [Dropdown](/ui/components/navigation/dropdown). Your application supplies country data and translated text and decides how selected identifiers map to stored values.

<StoryEmbed storyId="ui-input-countryselect--display" title="Country selection" height={340} />

## Usage

Supply prepared choices with unique stable string identifiers and display labels. The picker reports identifiers unchanged, independently of the displayed language. Flags are optional decorative React nodes. A choice can set `disabled: true` to remain visible without allowing selection.

```tsx theme={null}
import { CountrySelect } from 'twenty-ui/components/input';

const COUNTRIES = [
  { value: 'FR', label: 'France', flag: '🇫🇷' },
  { value: 'DE', label: 'Germany', flag: '🇩🇪' },
  { value: 'JP', label: 'Japan', flag: '🇯🇵' },
];

export const ShippingCountry = () => (
  <CountrySelect
    label="Shipping country"
    countries={COUNTRIES}
    defaultValue="FR"
    searchLabel="Search countries"
    noCountryLabel="No country"
    noResultsLabel="No countries found"
  />
);
```

`defaultValue` initializes uncontrolled selection. Use `value` and `onValueChange` for controlled selection. Updating `value` externally updates both the trigger and the selected option, including while the popup is open.

```tsx theme={null}
import { useState } from 'react';
import { CountrySelect, type CountryChoice } from 'twenty-ui/components/input';

export const ControlledCountry = ({
  countries,
}: {
  countries: readonly CountryChoice[];
}) => {
  const [country, setCountry] = useState<string | null>('FR');

  return (
    <CountrySelect
      label="Country"
      countries={countries}
      value={country}
      onValueChange={setCountry}
    />
  );
};
```

## Shared parts

`Root` owns the prepared choices and selected identifier. It adds no DOM element. `Trigger` reads that selection and displays its label and flag. `Options` reads the same data and renders searchable rows inside `Dropdown.SelectionGroup`. Dropdown owns opening, popup placement, pages, navigation, and focus.

```tsx theme={null}
import { CountrySelect, type CountryChoice } from 'twenty-ui/components/input';
import { Dropdown } from 'twenty-ui/components/navigation';

export const ShippingCountryParts = ({
  countries,
}: {
  countries: readonly CountryChoice[];
}) => (
  <CountrySelect.Root countries={countries} defaultValue="FR">
    <Dropdown.Root type="picker">
      <CountrySelect.Trigger label="Shipping country" />
      <Dropdown.Content>
        <CountrySelect.Options
          aria-label="Shipping country"
          searchLabel="Search countries"
        />
      </Dropdown.Content>
    </Dropdown.Root>
  </CountrySelect.Root>
);
```

The shorthand assembles these parts internally. Both forms share selection, clearing, disabled behavior, search matching, and selected-option presentation. `closeOnSelect` defaults to `true`. Set it to `false` on the shorthand or `Options` when selection should keep the popup open.

## Clearing and cancellation

`null` represents no country. Selecting the no-country row reports `null`; passing `value={null}` clears a controlled picker. An empty string (`''`) is a valid supplied choice identifier and stays distinct from `null`.

`onValueChange` receives the next identifier or `null` and Dropdown selection event details: `event`, `reason`, `trigger`, `cancel`, and `isCanceled`. Call `details.cancel()` before updating controlled state to preserve the current selection and keep the popup open. Repeated selection of the current identifier does not emit another value change.

An identifier absent from `countries` displays `noCountryLabel` and the no-country icon. Rendering it does not emit a change or overwrite the host value. The application decides how to handle stored values absent from the current choices.

## Search and interaction

Search matches prepared display labels. It ignores letter case, optional accents in Latin, Greek, Arabic, and Hebrew text, and Cyrillic stress marks, treats the Cyrillic `ё` as `е`, and folds letters such as `ø`, `æ`, and `ß` into `o`, `ae`, and `ss`. It also ignores standalone accent characters such as `^`, `` ` ``, `´`, `¨`, and `ˇ`, so a pending dead key does not hide matches. Marks that form distinct letters remain significant, such as Japanese voicing marks, Indic vowel signs, and the Cyrillic `й`. A partly typed Korean syllable, such as ㅎ or 하, matches syllables that start with it, such as 한. `noCountryLabel` participates in the same search. A query with no matching options displays `noResultsLabel`.

Arrow keys, Home, and End move focus without changing the selected identifier. Enter or Space on an enabled option commits selection through its selection group. Escape and outside interaction use Dropdown dismissal and focus handling. Each picker has independent selection and popup state.

Dropdown search activation and IME protection still need integration work, as do lazy page sessions and inactive-page behavior. Both picker forms reset their own search on reopen, including popups kept mounted. Their shared implementation does not yet complete Dropdown's page and browsing lifecycle.

`disabled` on `Root` or the shorthand prevents opening and selection. A picker with no supplied countries is disabled as well. If either condition occurs while its popup is open, the picker requests closure. Closing returns focus to the trigger. Controlled hosts must accept that close request through `onOpenChange`; the shorthand also suppresses the popup while disabled. After accepting the close request, enabling the picker again does not reopen it automatically. The trigger stays focusable and exposes `aria-disabled`. Disabled triggers skip consumer activation handlers. Function forms of `className`, `style`, and `render` receive `disabled: true` in the trigger state.

## Labels and customization

Provide a visible `label`, or use `aria-label` or `aria-labelledby` on the trigger. Referenced labels take precedence over a nonempty `aria-label`, which takes precedence over the visible label. With one of these names, the selected country describes the trigger. Otherwise the selected country names it. The popup inherits the trigger name unless caller-owned popup labels or a Dropdown title name it. The shorthand also names its options listbox from those trigger labels unless `optionsProps` supplies its own accessible name. In composed usage, give `Options` an `aria-label` or `aria-labelledby` for an explicit listbox name. Otherwise it references the registered Dropdown title or trigger content. A tooltip reveals the full selected label when it is truncated.

`searchLabel` supplies the search input's placeholder and accessible name, `noCountryLabel` the no-country text, and `noResultsLabel` the empty-result text. They default to `Search`, `No country`, and `No results`. Pass translated strings when your application is localized. Custom `Trigger` children replace the selected country display; the caller owns that content and its accessible presentation.

Native trigger attributes, event handlers, `className`, `style`, `ref`, and `render` target the trigger button. `wrapperProps` targets the surrounding div that also contains the visible label. `Options` native props, `ref`, and `render` target its selection group div. Its `render` function receives `{ value, multiple: false }`, where `value` is the public country identifier or `null`, rather than an internal option identifier. `searchProps` targets the search input; search value and change handling remain owned by the picker. The shorthand accepts the same search props directly and group attributes through `optionsProps`. Forward supplied props and refs when replacing an element through `render`.

## Host integration and storage

The shorthand accepts Dropdown's `open`, `defaultOpen`, `onOpenChange`, `onEscapeKeyDown`, `onInteractOutside`, and `defaultPage`. Composed usage sets them on `Dropdown.Root`. `onOpenChange` currently reports only a boolean, matching Dropdown's public contract. Opening event details and general opening cancellation remain unavailable upstream. Escape and outside dismissal callbacks can call `event.preventDefault()` to keep the popup open.

`popupProps` accepts all `Dropdown.Content` props except `children`, including popup native attributes, `ref`, `render`, positioning, portal destination, accessible names, and `keepMounted`. In composed usage, pass them directly to `Dropdown.Content`.

Keep dropdown IDs, focus stacks, field metadata, persistence, and editing lifecycle in the host adapter. Twenty's frontend adapter uses stable country codes for picker identifiers and converts explicit selections back to existing source country-name values. Its stored empty string maps to picker `null`; unmatched legacy values stay stored until an explicit selection changes them. Translated display labels do not become stored country names. Applications with another storage format should make the conversion in their adapter as well.

Native form submission, reset, `required`, `readOnly`, and Field integration for this custom picker remain deferred.

## Front component renderer

React and Preact front component fixtures verify pointer opening, filtering, and selection through the shorthand and compound APIs, including controlled and uncontrolled values, null clearing and reselection, canceled changes, external controlled updates, disabled interaction, translated labels, and decorative flags. They also verify native trigger and search input props and refs, plus trigger and option-group render targets and refs. Search keyboard activation and IME handling, page-session lifetime, and full renderer portal, geometry, scrolling, outside dismissal, and focus restoration acceptance remain open.

## Migration

Replace the previous empty-string no-country sentinel with `null` in controlled values and selection state. An empty string (`''`) is now a valid choice identifier, so `value=""` no longer means no country. Keep each `CountryChoice.value` stable; existing string identifiers can remain unchanged and display labels can be translated independently.

Update `onValueChange` handlers to accept `string | null`. The callback now also receives `eventDetails` as its second argument, including `cancel()` to reject a selection. Handlers that do not need event details can continue using only the first argument.

If your storage uses `''` for no country, convert it to `null` before passing it to the picker and convert reported `null` back to `''` when persisting. Keep that compatibility conversion in the application adapter, and omit it when `''` is an intentional choice identifier.

## Props

<ParamField body="children" type="ReactNode">
  Custom trigger content. Replaces the selected country presentation while retaining shared trigger behavior.
</ParamField>

<ParamField body="className" type="string | ((state: PopoverTriggerState) => string | undefined)">
  Class applied to the trigger.
</ParamField>

<ParamField body="closeDelay" type="number" default="0">
  How long to wait before closing the popover that was opened on hover.
  Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="closeOnSelect" type="boolean">
  Closes the popup after a selection. Defaults to true. Cancelled selection keeps it open.
</ParamField>

<ParamField body="countries" type="readonly CountryChoice[]" required>
  Prepared country choices with unique stable string values, caller-owned labels, and optional decorative flags and disabled states. An empty string is a valid choice value.
</ParamField>

<ParamField body="defaultOpen" type="boolean">
  Whether the uncontrolled popup is initially open.
</ParamField>

<ParamField body="defaultPage" type="string">
  Dropdown page shown when the popup opens. Defaults to root.
</ParamField>

<ParamField body="defaultValue" type="string | null">
  Initial uncontrolled selected identifier. Defaults to null.
</ParamField>

<ParamField body="delay" type="number" default="300">
  How long to wait before the popover may be opened on hover. Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="disabled" type="boolean">
  Prevents opening and selection while the trigger stays focusable. An empty country list is also disabled.
</ParamField>

<ParamField body="id" type="string">
  ID of the trigger. Generated when omitted.
</ParamField>

<ParamField body="label" type="string">
  Optional visible label that names the trigger.
</ParamField>

<ParamField body="nativeButton" type="boolean" default="true">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `false` if the rendered element is not a button (e.g. `<div>`).
</ParamField>

<ParamField body="noCountryLabel" type="string">
  Text of the no-country choice, also displayed when no country is selected.
</ParamField>

<ParamField body="noResultsLabel" type="string">
  Text displayed when no choice matches the search.
</ParamField>

<ParamField body="onEscapeKeyDown" type="((event: DropdownDismissEvent) => void)">
  Called before Escape dismisses the popup. Call event.preventDefault() to keep it open.
</ParamField>

<ParamField body="onInteractOutside" type="((event: DropdownDismissEvent) => void)">
  Called before outside interaction or Tab dismisses the popup. Call event.preventDefault() to keep it open.
</ParamField>

<ParamField body="onOpenChange" type="((open: boolean) => void)">
  Reports popup opening and closing with the next boolean state, matching Dropdown.Root.
</ParamField>

<ParamField body="onValueChange" type="((value: string | null, eventDetails: DropdownSelectionGroupChangeEventDetails) => void)">
  Reports the selected identifier or null with cancellable event details. Call details.cancel() to preserve selection and keep the popup open.
</ParamField>

<ParamField body="open" type="boolean">
  Controls whether the country popup is open.
</ParamField>

<ParamField body="openOnHover" type="boolean" default="false">
  Whether the popover should also open when the trigger is hovered.
</ParamField>

<ParamField body="optionsProps" type="Omit<CountrySelectOptionsProps, &#x22;closeOnSelect&#x22; | &#x22;searchLabel&#x22; | &#x22;noResultsLabel&#x22; | &#x22;searchProps&#x22;>">
  Native props, ref, and render for the selection group div rendered by CountrySelect.Options.
</ParamField>

<ParamField body="popupProps" type="Omit<DropdownContentProps, &#x22;children&#x22;>">
  Dropdown.Content props, including native popup attributes, ref, render, positioning, portal destination, and keepMounted. Explicit popup accessible names override the trigger name. Children are owned by the picker.
</ParamField>

<ParamField body="ref" type="Ref<HTMLButtonElement>">
  Ref to the trigger element.
</ParamField>

<ParamField body="render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>">
  Replaces the trigger element while preserving its behavior and props.
</ParamField>

<ParamField body="searchLabel" type="string">
  Placeholder and accessible name of the search input.
</ParamField>

<ParamField body="searchProps" type="Omit<DropdownSearchProps, &#x22;defaultValue&#x22; | &#x22;value&#x22; | &#x22;onValueChange&#x22;>">
  Native search input props, ref, and render. Search value and change handling are owned by the picker.
</ParamField>

<ParamField body="style" type="CSSProperties | ((state: PopoverTriggerState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

<ParamField body="value" type="string | null">
  Controlled selected identifier. Null selects no country; undefined leaves selection uncontrolled.
</ParamField>

<ParamField body="wrapperProps" type="Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, &#x22;children&#x22;>">
  Native props and ref for the div around the trigger and its visible label. Children are owned by the picker.
</ParamField>

### CountrySelect.Root

<ParamField body="Root.children" type="ReactNode" required>
  Picker composition containing Dropdown.Root, CountrySelect.Trigger, and CountrySelect.Options. Root adds no DOM element.
</ParamField>

<ParamField body="Root.countries" type="readonly CountryChoice[]" required>
  Prepared country choices with unique stable string values, caller-owned labels, and optional decorative flags and disabled states. An empty string is a valid choice value.
</ParamField>

<ParamField body="Root.defaultValue" type="string | null" default="null">
  Initial uncontrolled selected identifier. Defaults to null.
</ParamField>

<ParamField body="Root.disabled" type="boolean" default="false">
  Prevents opening and selection. An empty country list is also disabled. The trigger stays focusable with aria-disabled.
</ParamField>

<ParamField body="Root.noCountryLabel" type="string" default="No country">
  Text for the null selection and its option. Defaults to No country.
</ParamField>

<ParamField body="Root.onValueChange" type="((value: string | null, eventDetails: DropdownSelectionGroupChangeEventDetails) => void)">
  Reports the selected identifier or null with event, reason, trigger, cancel and isCanceled details. Call cancel() to preserve selection and keep the popup open.
</ParamField>

<ParamField body="Root.value" type="string | null">
  Controlled selected identifier. Null selects no country; undefined leaves selection uncontrolled.
</ParamField>

### CountrySelect.Trigger

<ParamField body="Trigger.children" type="ReactNode">
  Custom trigger content. Replaces the selected country presentation while retaining shared trigger behavior.
</ParamField>

<ParamField body="Trigger.className" type="string | ((state: PopoverTriggerState) => string | undefined)">
  Class applied to the trigger button.
</ParamField>

<ParamField body="Trigger.closeDelay" type="number" default="0">
  How long to wait before closing the popover that was opened on hover.
  Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="Trigger.delay" type="number" default="300">
  How long to wait before the popover may be opened on hover. Specified in milliseconds.

  Requires the `openOnHover` prop.
</ParamField>

<ParamField body="Trigger.id" type="string">
  ID of the trigger. In addition to being forwarded to the rendered element,
  it is also used to specify the active trigger for the popover in controlled mode (with the PopoverRoot `triggerId` prop).
</ParamField>

<ParamField body="Trigger.label" type="string">
  Optional visible label that names the trigger.
</ParamField>

<ParamField body="Trigger.nativeButton" type="boolean" default="true">
  Whether the component renders a native `<button>` element when replacing it
  via the `render` prop.
  Set to `false` if the rendered element is not a button (e.g. `<div>`).
</ParamField>

<ParamField body="Trigger.openOnHover" type="boolean" default="false">
  Whether the popover should also open when the trigger is hovered.
</ParamField>

<ParamField body="Trigger.ref" type="Ref<HTMLButtonElement>">
  Ref to the trigger element.
</ParamField>

<ParamField body="Trigger.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>">
  Replaces the trigger element. Custom components must forward the supplied props and ref.
</ParamField>

<ParamField body="Trigger.style" type="CSSProperties | ((state: PopoverTriggerState) => CSSProperties | undefined)">
  Style applied to the element, or a function that
  returns a style object based on the component's state.
</ParamField>

<ParamField body="Trigger.wrapperProps" type="Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, &#x22;children&#x22;>">
  Native props and ref for the div around the trigger and its visible label. Children are owned by the picker.
</ParamField>

### CountrySelect.Options

<ParamField body="Options.aria-label" type="string">
  Accessible name for the selection listbox. Overrides its default name.
</ParamField>

<ParamField body="Options.aria-labelledby" type="string">
  Accessible name references for the selection listbox. Defaults to the registered Dropdown title or trigger element.
</ParamField>

<ParamField body="Options.className" type="string">
  Class applied to the selection group div.
</ParamField>

<ParamField body="Options.closeOnSelect" type="boolean">
  Closes the popup after a selection. Defaults to true. Cancelled selection keeps it open.
</ParamField>

<ParamField body="Options.noResultsLabel" type="string">
  Text displayed when no choice matches the search. Defaults to No results.
</ParamField>

<ParamField body="Options.ref" type="Ref<HTMLDivElement>">
  Ref to the selection group element.
</ParamField>

<ParamField body="Options.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, CountrySelectOptionsState>">
  Replaces the selection group div. Functions receive supplied props and \{ value, multiple: false } state, with the public string or null selection. Custom components must forward the supplied props and ref.
</ParamField>

<ParamField body="Options.searchLabel" type="string">
  Search placeholder and accessible name. Defaults to Search.
</ParamField>

<ParamField body="Options.searchProps" type="Omit<DropdownSearchProps, &#x22;defaultValue&#x22; | &#x22;value&#x22; | &#x22;onValueChange&#x22;>">
  Native search input props, ref, and render. Search value and change handling are owned by the picker.
</ParamField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.