# PhoneInput
Package: @mantine/core
Import: import { PhoneInput } from '@mantine/core';
Description: International phone number input with country selector

## Usage

`PhoneInput` provides an international phone number input with country selector
and formatting masks.

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Input features

PhoneInput component supports [Input](https://mantine.dev/core/input) and [Input.Wrapper](https://mantine.dev/core/input) components features and all input element props. PhoneInput documentation does not include all features supported by the component – see [Input](https://mantine.dev/core/input) documentation to learn about all available features.

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
       variant="default" size="sm" radius="md" label="Input label" withAsterisk={false} description="Input description" error=""
      placeholder="Your phone number"
      defaultCountry="us"
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Loading state

Set `loading` prop to display a loading indicator in the input:

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      loading
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Controlled

Use `value` and `onChange` props to control the input value. The `onChange` callback
receives the raw value and a details object containing the formatted value and country data.

```tsx
import { useState } from 'react';
import { Code, PhoneInput, PhoneInputCountryData } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState('+1');
  const [country, setCountry] = useState<PhoneInputCountryData | null>(null);
  const [formattedValue, setFormattedValue] = useState('');

  return (
    <>
      <PhoneInput
        label="Phone number"
        placeholder="Your phone number"
        defaultCountry="us"
        value={value}
        onChange={(rawValue, details) => {
          setValue(rawValue);
          setFormattedValue(details.formattedValue);
          setCountry(details.country);
        }}
        renderFlag={(c) => (
          <img
            src={`https://hatscripts.github.io/circle-flags/flags/${c?.iso2 ?? 'xx'}.svg`}
            alt={c?.name ?? 'Select country'}
            style={{ width: 20, height: 20, borderRadius: 20 }}
          />
        )}
      />
      <Code block mt="md">
        {[
          `Raw value: ${value}`,
          `Formatted: ${formattedValue}`,
          `Country: ${country?.iso2 ?? '–'}`,
        ].join('\\n')}
      </Code>
    </>
  );
}
```


## Preferred countries

Use `preferredCountries` prop to pin countries to the top of the dropdown.

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      preferredCountries={['us', 'gb', 'ca']}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Filter countries

Use `onlyCountries` to show only specific countries or `excludeCountries` to hide specific countries.

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      onlyCountries={['us', 'gb', 'ca', 'au', 'de', 'fr']}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="de"
      excludeCountries={['us', 'gb']}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Custom flags

Use `renderFlag` prop to render custom flag elements, for example emoji flags.
The function receives country data and should return a React node.

```tsx
import { PhoneInput } from '@mantine/core';

function getEmojiFlag(iso2: string) {
  return iso2
    .toUpperCase()
    .split('')
    .map((char) => String.fromCodePoint(0x1f1e6 + char.charCodeAt(0) - 65))
    .join('');
}

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      renderFlag={(country) => (
        <span style={{ fontSize: 20, lineHeight: 1 }}>
          {country ? getEmojiFlag(country.iso2) : '🌐'}
        </span>
      )}
    />
  );
}
```


## Searchable

Country dropdown search is enabled by default. Set `searchable={false}` to disable it.

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      searchable={false}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Country auto-detection

Set `withCountryAutoDetect` to automatically detect and switch the country when the user
types a dial code. For example, typing `+44` will switch to United Kingdom.
Without the prop, the country still changes when a number starting with `+` and another dial code is typed or pasted.
Area codes, for example `+1 204` for Canada, are detected only with the prop:

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Type +44 to switch to UK"
      defaultCountry="us"
      withCountryAutoDetect
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Localization

Use `renderCountryOption` to customize how countries are displayed in the dropdown.
This can be used to translate country names to a different language.

`getCountryLabel` returns the translated name of a country. It is used as the
country button label for screen readers and in the dropdown search, so users
can search by translated names. `searchPlaceholder` and `selectCountryLabel`
translate the search input placeholder and the button label shown before a
country is selected:

```tsx
import { PhoneInput } from '@mantine/core';

const COUNTRIES_ES: Record<string, string> = {
  us: 'Estados Unidos',
  gb: 'Reino Unido',
  es: 'España',
  mx: 'México',
  ar: 'Argentina',
};

function Demo() {
  return (
    <PhoneInput
      label="Número de teléfono"
      placeholder="Tu número de teléfono"
      defaultCountry="es"
      onlyCountries={['us', 'gb', 'es', 'mx', 'ar']}
      nothingFoundMessage="No se encontraron países"
      searchPlaceholder="Buscar países"
      selectCountryLabel="Seleccionar país"
      getCountryLabel={(country) => COUNTRIES_ES[country.iso2] ?? country.name}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country ? COUNTRIES_ES[country.iso2] ?? country.name : 'Seleccionar país'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
      renderCountryOption={(country) => (
        <span style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%' }}>
          <img
            src={`https://hatscripts.github.io/circle-flags/flags/${country.iso2}.svg`}
            alt={COUNTRIES_ES[country.iso2] ?? country.name}
            style={{ width: 20, height: 20, borderRadius: 20 }}
          />
          <span style={{ flex: 1 }}>{COUNTRIES_ES[country.iso2] ?? country.name}</span>
          <span style={{ opacity: 0.5, fontSize: 'var(--mantine-font-size-xs)' }}>
            +{country.dialCode}
          </span>
        </span>
      )}
    />
  );
}
```


## Dropdown position

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      comboboxProps={{ position: 'top', middlewares: { flip: false, shift: false } }}
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Use within Popover

To use `PhoneInput` within `Popover` or other overlay, set `comboboxProps={{ withinPortal: false }}`.
Pressing `Escape` while the country list is open closes both the list and the `Popover`.
Set `closeOnEscape={false}` on the `Popover` to keep it open. The country list still closes on `Escape`:

```tsx
import { Button, PhoneInput, Popover } from '@mantine/core';

function Demo() {
  return (
    <Popover width={300} position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button>Toggle popover</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <PhoneInput
          label="Phone number"
          placeholder="Your phone number"
          defaultCountry="us"
          comboboxProps={{ withinPortal: false }}
          renderFlag={(country) => (
            <img
              src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
              alt={country?.name ?? 'Select country'}
              style={{ width: 20, height: 20, borderRadius: 20 }}
            />
          )}
        />
      </Popover.Dropdown>
    </Popover>
  );
}
```


## Disabled state

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      defaultValue="+12025551234"
      disabled
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Read only

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <PhoneInput
      label="Phone number"
      placeholder="Your phone number"
      defaultCountry="us"
      defaultValue="+12025551234"
      readOnly
      renderFlag={(country) => (
        <img
          src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
          alt={country?.name ?? 'Select country'}
          style={{ width: 20, height: 20, borderRadius: 20 }}
        />
      )}
    />
  );
}
```


## Error state

```tsx
import { PhoneInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <PhoneInput
        label="Boolean error"
        placeholder="Your phone number"
        defaultCountry="us"
        error
        renderFlag={(country) => (
          <img
            src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
            alt={country?.name ?? 'Select country'}
            style={{ width: 20, height: 20, borderRadius: 20 }}
          />
        )}
      />
      <PhoneInput
        mt="md"
        label="With error message"
        placeholder="Your phone number"
        defaultCountry="us"
        error="Invalid phone number"
        renderFlag={(country) => (
          <img
            src={`https://hatscripts.github.io/circle-flags/flags/${country?.iso2 ?? 'xx'}.svg`}
            alt={country?.name ?? 'Select country'}
            style={{ width: 20, height: 20, borderRadius: 20 }}
          />
        )}
      />
    </>
  );
}
```



#### Props

**PhoneInput props**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| comboboxProps | ComboboxProps | - | Props passed down to the underlying Combobox component |
| defaultCountry | string | - | Default country ISO2 code, e.g. "us" |
| defaultValue | string | - | Uncontrolled default value |
| description | React.ReactNode | - | Contents of `Input.Description` component. If not set, description is not displayed. |
| descriptionProps | InputDescriptionProps & DataAttributes & PlaceholderPolymorphicProps | - | Props passed down to the `Input.Description` component |
| disabled | boolean | - | Sets `disabled` attribute on the `input` element |
| dropdownOpened | boolean | - | Controlled dropdown opened state |
| error | React.ReactNode | - | Contents of `Input.Error` component. If not set, error is not displayed. |
| errorProps | InputErrorProps & DataAttributes & PlaceholderPolymorphicProps | - | Props passed down to the `Input.Error` component |
| excludeCountries | string[] | - | Hide these countries, ISO2 codes |
| getCountryLabel | (country: PhoneInputCountryData) => string | - | Returns a localized country name, used as the country button `aria-label` and matched in search in addition to the English name |
| inputContainer | (children: ReactNode) => ReactNode | - | Render function to wrap the input element. Useful for adding tooltips, popovers, or other wrappers around the input. |
| inputSize | string | - | HTML `size` attribute for the input element (number of visible characters) |
| inputWrapperOrder | ("input" \| "label" \| "description" \| "error")[] | - | Controls order and visibility of wrapper elements. Only elements included in this array will be rendered. |
| international | boolean | - | Show +dialCode in input |
| label | React.ReactNode | - | Contents of `Input.Label` component. If not set, label is not displayed. |
| labelProps | InputLabelProps & DataAttributes & PlaceholderPolymorphicProps | - | Props passed down to the `Input.Label` component |
| leftSection | React.ReactNode | - | Content section displayed on the left side of the input |
| leftSectionPointerEvents | React.CSSProperties["pointerEvents"] | - | Sets `pointer-events` styles on the `leftSection` element. Use `'all'` when section contains interactive elements (buttons, links). |
| leftSectionProps | React.ComponentProps<"div"> | - | Props passed down to the `leftSection` element |
| leftSectionWidth | React.CSSProperties["width"] | - | Left section width, used to set `width` of the section and input `padding-left`, by default equals to the input height |
| loaderProps | LoaderProps | - | Props passed down to the `Loader` component displayed when `loading` prop is set |
| loading | boolean | - | Displays loading indicator in the left or right section |
| loadingPosition | "left" \| "right" | - | Position of the loading indicator |
| maxDropdownHeight | string \| number | - | Maximum dropdown height in px |
| nothingFoundMessage | React.ReactNode | - | Message displayed when no countries match the search query |
| onChange | (rawValue: string, details: PhoneInputChangeDetails) => void | - | Called when value changes |
| onCountryChange | (country: PhoneInputCountryData) => void | - | Called when selected country changes |
| onDropdownClose | () => void | - | Called when dropdown closes |
| onDropdownOpen | () => void | - | Called when dropdown opens |
| onlyCountries | string[] | - | Only show these countries, ISO2 codes |
| pointer | boolean | - | Determines whether the input should have `cursor: pointer` style. Use when input acts as a button-like trigger (e.g., `component="button"` for Select/DatePicker). |
| preferredCountries | string[] | - | Countries pinned to top of dropdown with separator, ISO2 codes |
| radius | MantineRadius \| number | - | Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem |
| renderCountryOption | (country: PhoneInputCountryData) => ReactNode | - | Custom renderer for country dropdown options |
| renderFlag | (country: PhoneInputCountryData \| null) => ReactNode | - | Custom flag renderer for country selector button and dropdown options, receives null when no country is selected |
| required | boolean | - | Adds required attribute to the input and a red asterisk on the right side of label |
| rightSection | React.ReactNode | - | Content section displayed on the right side of the input |
| rightSectionPointerEvents | React.CSSProperties["pointerEvents"] | - | Sets `pointer-events` styles on the `rightSection` element. Use `'all'` when section contains interactive elements (buttons, links). |
| rightSectionProps | React.ComponentProps<"div"> | - | Props passed down to the `rightSection` element |
| rightSectionWidth | React.CSSProperties["width"] | - | Right section width, used to set `width` of the section and input `padding-right`, by default equals to the input height |
| searchPlaceholder | string | - | Placeholder of the search input in the dropdown |
| searchable | boolean | - | Enable search in country dropdown |
| selectCountryLabel | string | - | `aria-label` of the country button when no country is selected |
| size | MantineSize | - | Controls input `height`, horizontal `padding`, and `font-size` |
| success | React.ReactNode | - | Contents of `Input.Success` component. If not set, success is not displayed. |
| successProps | InputSuccessProps & DataAttributes & PlaceholderPolymorphicProps | - | Props passed down to the `Input.Success` component |
| value | string | - | Controlled value — raw digits with + prefix, e.g. "+12025551234" |
| withAsterisk | boolean | - | If set, the required asterisk is displayed next to the label. Overrides `required` prop. Does not add required attribute to the input. |
| withChevron | boolean | - | Display chevron in the country button |
| withCountryAutoDetect | boolean | - | Auto-detect country from typed dial code digits |
| withErrorStyles | boolean | - | Determines whether the input should have red border and red text color when the `error` prop is set |
| withSuccessStyles | boolean | - | Determines whether the input should have green border when the `success` prop is set |
| wrapperProps | WrapperProps | - | Props passed down to the root element |


#### Styles API

PhoneInput component supports Styles API. With Styles API, you can customize styles of any inner element. Follow the documentation to learn how to use CSS modules, CSS variables and inline styles to get full control over component styles.

**PhoneInput selectors**

| Selector | Static selector | Description |
|----------|----------------|-------------|
| wrapper | .mantine-PhoneInput-wrapper | Root element of the Input |
| input | .mantine-PhoneInput-input | Input element |
| section | .mantine-PhoneInput-section | Left and right sections |
| bottomSection | .mantine-PhoneInput-bottomSection | Bottom section element, rendered inside the input border at the bottom |
| root | .mantine-PhoneInput-root | Root element |
| label | .mantine-PhoneInput-label | Label element |
| required | .mantine-PhoneInput-required | Required asterisk element, rendered inside label |
| description | .mantine-PhoneInput-description | Description element |
| error | .mantine-PhoneInput-error | Error element |
| success | .mantine-PhoneInput-success | Success element |
| countryButton | .mantine-PhoneInput-countryButton | Country selector button in leftSection |
| countryButtonFlag | .mantine-PhoneInput-countryButtonFlag | Flag wrapper inside country button |
| countryButtonCode | .mantine-PhoneInput-countryButtonCode | Country ISO2 code text inside country button |
| dropdown | .mantine-PhoneInput-dropdown | Combobox dropdown |
| search | .mantine-PhoneInput-search | Search input in dropdown |
| countryOption | .mantine-PhoneInput-countryOption | Country option content wrapper in dropdown |
| optionName | .mantine-PhoneInput-optionName | Country name text in option |
| optionDialCode | .mantine-PhoneInput-optionDialCode | Dial code text in option |
| divider | .mantine-PhoneInput-divider | Divider between preferred and other countries |
