> ## 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.

# PhoneCountryPicker

> A complete phone country picker and composable parts sharing country data and selection.

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>
  </>;

`PhoneCountryPicker` combines a country flag trigger, popup, search and calling-code choices. Use the complete component for ordinary selection, or compose `Root`, `Trigger` and `Options` with [Dropdown](/ui/components/navigation/dropdown) when you need a custom popup. Both forms share the same prepared countries and selection.

<StoryEmbed storyId="ui-input-phonecountrypicker--display" title="Phone country selection" height={260} />

## Usage

Supply your own country data and a trigger accessible name. Use `value` with `onValueChange` for controlled selection, or `defaultValue` for uncontrolled selection. `null` represents no selected country.

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

const COUNTRIES: PhoneCountryOption[] = [
  { value: 'FR', label: 'France', callingCode: '33', flag: '🇫🇷' },
  { value: 'GB', label: 'United Kingdom', callingCode: '44', flag: '🇬🇧' },
  { value: 'US', label: 'United States', callingCode: '1', flag: '🇺🇸' },
];

export const PhoneCountryExample = () => {
  const [value, setValue] = useState<string | null>('FR');

  return (
    <PhoneCountryPicker
      aria-label="Phone country"
      countries={COUNTRIES}
      value={value}
      onValueChange={setValue}
      contentProps={{ width: 280 }}
      optionsProps={{
        searchLabel: 'Search countries',
        listLabel: 'Phone countries',
        emptyLabel: 'No countries found',
      }}
    />
  );
};
```

Each `PhoneCountryOption` has a unique stable `value`, a localized country `label`, a digits-only `callingCode` without the leading plus sign, and a decorative `flag` node. It may also be `disabled`. Values are caller-owned identifiers, such as ISO country codes, and are returned unchanged. Labels, flags and calling codes are presentation data and do not become the selected identifier.

The picker adds the plus sign and isolates calling codes left to right in option rows and overflow tooltips, including in right-to-left interfaces. Supply countries in the order you want them displayed. The selected country appears first while it matches the query; the remaining matches retain their supplied order.

## Compound composition

This is a breaking API change for existing compound callers. Add `PhoneCountryPicker.Root` around `Dropdown.Root`, move `countries`, `value` and `onValueChange` from `Options` to `Root`, and remove `Trigger.country`. Both parts now require `Root` and derive the selected country from it.

`Root` owns countries and the controlled or uncontrolled selection. Put the shared `Dropdown.Root` inside it and compose the trigger, content and options. `Trigger` derives its selected flag and accessible description from `Root`; `Options` reads the same data and selection. Do not supply another country or selected value to those parts.

```tsx theme={null}
import {
  PhoneCountryPicker,
  type PhoneCountryOption,
} from 'twenty-ui/components/input';
import { Dropdown } from 'twenty-ui/components/navigation';
import { Button } from 'twenty-ui/primitives/input';

const COUNTRIES: PhoneCountryOption[] = [
  { value: 'FR', label: 'France', callingCode: '33', flag: '🇫🇷' },
  { value: 'GB', label: 'United Kingdom', callingCode: '44', flag: '🇬🇧' },
];

<PhoneCountryPicker.Root countries={COUNTRIES} defaultValue="FR">
  <Dropdown.Root type="picker">
    <PhoneCountryPicker.Trigger
      aria-label="Phone country"
      render={<Button variant="outline" />}
    />
    <Dropdown.Content width={320}>
      <PhoneCountryPicker.Options
        searchLabel="Search countries"
        listLabel="Phone countries"
        emptyLabel="No countries found"
      />
    </Dropdown.Content>
  </Dropdown.Root>
</PhoneCountryPicker.Root>;
```

Keep one root per selector. The shared Dropdown owns opening, popup placement, pages, focus and search navigation. Use `dropdownProps` and `contentProps` on the complete picker, or configure the Dropdown parts directly in compound usage.

## Search and selection

Search matches substrings of the supplied country labels, stable identifiers and calling codes. Labels and identifiers use the shared `normalizeSearchText` normalization: lowercase text, Unicode NFD decomposition and script-aware accent handling. For example, `reunion` matches `Réunion` and `gb` matches a `GB` identifier. Meaningful distinctions in scripts are preserved according to that helper. Surrounding query whitespace is trimmed; internal whitespace is preserved. For calling-code search, one leading plus sign is optional, so `44`, `+44` and `" +44 "` match the supplied calling code `44`. Search does not parse a full phone number or infer a country from it.

`onValueChange(value, eventDetails)` receives the requested identifier and the shared `Dropdown.SelectionGroup` details: `reason`, the native `event`, the selected option `trigger`, `cancel()` and `isCanceled`. Call `cancel()` before updating controlled state to reject the request. A canceled request preserves uncontrolled selection and leaves the popup open. Set `closeOnSelect={false}` to keep it open after an accepted selection. Selection changes do not modify any phone input value.

Set `disabled` on the complete picker or `Root` to disable its trigger, search and selection. A disabled country stays visible and cannot be selected. `Trigger.disabled` and `Options.disabled` can additionally disable those individual parts.

## Native targets and labels

The complete picker's native attributes, event handlers, `className`, `style`, `ref`, `render` and optional `children` target its trigger. `children` replaces the default flag or world icon and chevron. `contentProps` targets `Dropdown.Content`; `optionsProps` targets the options root div and supplies `searchLabel`, `listLabel` and `emptyLabel`. In compound usage, native props, refs and render composition target the corresponding `Trigger` button or `Options` div. `Root` renders no DOM node.

Supply localized country labels and the required trigger `aria-label`. The selected country label describes the trigger, while its flag and chevron are decorative. An empty selection shows a world icon. The popup takes the trigger name unless a `Dropdown.Title` or an explicit content label names it. `searchLabel` defaults to `Search` and names both the search field and its placeholder. `listLabel` defaults to `Countries` and names the listbox independently. `emptyLabel` defaults to `No results`.

## Phone input integration

The picker ships without a country catalog, flag asset package or phone-input runtime. The frontend adapter owns phone parsing, validation, formatting, editing drafts and persistence. Map the phone library's selected country to `Root.value` or the complete picker's `value`, and map country changes back through one callback. Keep the phone text in its existing owner when changing or clearing a country, or applying an external selected-country update.

Native form submission, reset, required, read-only and Field integration for this custom picker are deferred. The complete picker forwards native trigger attributes according to the Dropdown button contract.

## Front component renderer

The React and Preact gallery fixtures include the complete picker and compound composition. Portal placement, geometry, scrolling, dismissal and focus belong to the shared Dropdown and renderer integration. The picker does not introduce a separate renderer runtime.

## Props

<ParamField body="aria-label" type="string" required>
  Required accessible name for the country selector trigger.
</ParamField>

<ParamField body="children" type="ReactNode">
  Optional trigger content replacing the default country flag or world icon and chevron.
</ParamField>

<ParamField body="className" type="string | ((state: PopoverTriggerState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</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 dropdown after an accepted selection. Defaults to true.
</ParamField>

<ParamField body="contentProps" type="Omit<DropdownContentProps, &#x22;children&#x22;>">
  Props for the internally assembled Dropdown.Content, including its accessible name, placement, width and popup ref.
</ParamField>

<ParamField body="countries" type="readonly PhoneCountryOption[]" required>
  Prepared choices with a unique stable value, localized label, digits-only calling code without a plus sign, decorative flag node and optional disabled state. Matching choices retain their supplied order after the selected country.
</ParamField>

<ParamField body="defaultValue" type="string | null">
  Initial uncontrolled country identifier. Omit it or use null to start empty.
</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">
  Disables the trigger, search and country selection.
</ParamField>

<ParamField body="dropdownProps" type="Omit<DropdownRootProps, &#x22;children&#x22; | &#x22;type&#x22; | &#x22;multiple&#x22;>">
  Props for the internally assembled Dropdown.Root, including controlled open state and open-change events. The picker fixes its type to picker.
</ParamField>

<ParamField body="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="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="onValueChange" type="((value: string | null, eventDetails: DropdownSelectionGroupChangeEventDetails) => void)">
  Receives the requested country identifier or null and Dropdown.SelectionGroup event details, including reason, event, trigger and cancel(). Cancel before updating controlled state to reject a selection.
</ParamField>

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

<ParamField body="optionsProps" type="PhoneCountryPickerOptionsProps">
  Props for the options root div, including native attributes, ref, render, searchLabel, listLabel and emptyLabel. Search matches normalized labels and identifiers, or calling codes with an optional leading plus sign. Surrounding query whitespace is trimmed.
</ParamField>

<ParamField body="ref" type="Ref<HTMLButtonElement>">
  Ref to the trigger button, or the element composed through render.
</ParamField>

<ParamField body="render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>">
  Composes the trigger with an element or render callback. Native attributes, handlers, className, style and ref target the trigger.
</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 country identifier. Use null to clear the selection. Labels and calling codes do not replace this value.
</ParamField>

### PhoneCountryPicker.Root

<ParamField body="Root.children" type="ReactNode" required>
  A Dropdown.Root with type="picker", its Trigger, Content and Options. Root shares prepared data and selection and renders no DOM node.
</ParamField>

<ParamField body="Root.closeOnSelect" type="boolean" default="true">
  Closes the composed Dropdown after an accepted selection. Defaults to true.
</ParamField>

<ParamField body="Root.countries" type="readonly PhoneCountryOption[]" required>
  Prepared choices shared by Trigger and Options, with unique stable values, localized labels, digits-only calling codes without a plus sign, decorative flag nodes and optional disabled states.
</ParamField>

<ParamField body="Root.defaultValue" type="string | null" default="null">
  Initial uncontrolled country identifier. Omit it or use null to start empty.
</ParamField>

<ParamField body="Root.disabled" type="boolean" default="false">
  Disables the composed trigger, search and country selection.
</ParamField>

<ParamField body="Root.onValueChange" type="((value: string | null, eventDetails: DropdownSelectionGroupChangeEventDetails) => void)">
  Receives the requested identifier or null and Dropdown.SelectionGroup event details, including reason, event, trigger and cancel(). Cancel before updating controlled state to reject a selection.
</ParamField>

<ParamField body="Root.value" type="string | null">
  Controlled country identifier shared by the parts. Use null to clear the selection.
</ParamField>

### PhoneCountryPicker.Trigger

<ParamField body="Trigger.aria-label" type="string" required>
  Required accessible name for the country selector button.
</ParamField>

<ParamField body="Trigger.children" type="ReactNode">
  Optional trigger content replacing the selected country flag or empty world icon and chevron. The selected label comes from Root.
</ParamField>

<ParamField body="Trigger.className" type="string | ((state: PopoverTriggerState) => string | undefined)">
  CSS class applied to the element, or a function that
  returns a class based on the component's state.
</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.disabled" type="boolean">
  Disables the native trigger button in addition to Root.disabled.
</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.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 button, or the element composed through render.
</ParamField>

<ParamField body="Trigger.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>">
  Composes the Dropdown trigger with an element or render callback. Native attributes, handlers, className, style and ref target this element.
</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>

### PhoneCountryPicker.Options

<ParamField body="Options.disabled" type="boolean">
  Disables search and selection in addition to Root.disabled.
</ParamField>

<ParamField body="Options.emptyLabel" type="string" default="No results">
  Status message shown when no country label, identifier or calling code matches. Defaults to No results.
</ParamField>

<ParamField body="Options.listLabel" type="string" default="Countries">
  Accessible name of the country listbox, separate from the search field. Defaults to Countries.
</ParamField>

<ParamField body="Options.ref" type="Ref<HTMLDivElement>">
  Ref to the options root div, or the element composed through render.
</ParamField>

<ParamField body="Options.render" type="ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>">
  Composes the non-interactive options root with an element or render callback. Native attributes, handlers, className, style and ref target this element. searchLabel names the internal search input.
</ParamField>

<ParamField body="Options.searchLabel" type="string" default="Search">
  Accessible name and placeholder of the country search field. Defaults to Search. Search matches normalized labels and identifiers, or calling codes with an optional leading plus sign. Surrounding query whitespace is trimmed.
</ParamField>


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