# CountrySelect

> Searchable country combobox with SVG flags and names in English or Arabic. The value is the ISO 3166-1 alpha-2 code. Uses PHONE_COUNTRIES or a LocationsDataSource.

Source: https://docs.nasaqui.com/components/country-select

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/country-select.json
```

A single combobox for choosing a country. Each item shows its flag ([`CountryFlag`](https://docs.nasaqui.com/components/country-flag)) and its
name in the active language; typing filters by English name, Arabic name or ISO code. The value is the ISO code
(`"SA"`).

## When to use

- A country field on its own: nationality, country of residence, shipping country.

## When not to use

- A full address with city and area: use [`AddressInput`](https://docs.nasaqui.com/components/address-input).
- A phone number: use [`PhoneInput`](https://docs.nasaqui.com/components/phone-input), which has its own country list.

## Import

```tsx
import { CountrySelect } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { CountrySelect, Field, FieldLabel } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Residence() {
  const [iso, setIso] = useState("");
  return (
    <Field>
      <FieldLabel>Country</FieldLabel>
      <CountrySelect value={iso} onValueChange={setIso} />
    </Field>
  );
}
```

## Anatomy

```
CountrySelect               div (display: contents)   data-slot="country-select"
├─ input group              Combobox input, clear, chevron
├─ popup                    list of CountryFlag + name
└─ hidden input             only when `name` is set
```

## API

### `CountrySelect`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value?` | `string` | none | Controlled ISO alpha-2 code, `""` for none. |
| `defaultValue?` | `string` | `""` | Initial code when uncontrolled. |
| `onValueChange?` | `(iso: string) => void` | none | Called with the code, `""` when cleared. |
| `countries?` | `readonly PhoneCountry[]` | `PHONE_COUNTRIES` | The list to offer. Ignored when `dataSource` is set. |
| `dataSource?` | `LocationsDataSource` | none | Load countries from a locations source; items need an `iso2`. Create it once. |
| `disabled?` / `invalid?` | `boolean` | `false` | Disabled state; `aria-invalid`. |
| `name?` | `string` | none | Renders a hidden input with the ISO code. |
| `id?` / `placeholder?` / `aria-label?` | `string` | none | Input id, placeholder (default "Select a country"), name when there is no `FieldLabel`. |
| `locale?` / `dir?` | `string` / `"ltr" \| "rtl"` | from the provider | Override for strings, names and popup direction. |
| `className?` | `string` | none | Merged onto the wrapper (which is `display: contents`). |

## Examples

### Gulf countries only

```tsx
import { CountrySelect, PHONE_COUNTRIES } from "@fadymondy/nasaq/web";

const gcc = PHONE_COUNTRIES.filter((c) => ["SA", "AE", "KW", "QA", "BH", "OM"].includes(c.iso));

export const Gulf = () => <CountrySelect countries={gcc} aria-label="Country" />;
```

### From the hub

```tsx
import { CountrySelect, createHubLocationsDataSource } from "@fadymondy/nasaq/web";

const hub = createHubLocationsDataSource();

export const FromHub = () => <CountrySelect dataSource={hub} aria-label="Country" />;
```

## Accessibility

The input is a Base UI combobox and a Field control, so `FieldLabel` names it. Flags are decorative; the name is text.

| Key | Action |
| --- | --- |
| `Down` / typing | Opens the list and filters it. |
| `Up` / `Down` | Moves the highlighted country. |
| `Enter` | Selects it. |
| `Esc` | Closes the list. |

Caller must localise `aria-label` and `placeholder`.

## RTL & i18n

Names follow the active language (English or Arabic, falling back to the other when empty); search matches both. The
popup takes the direction of the provider.

## Styling & tokens

Same as [`Combobox`](https://docs.nasaqui.com/components/combobox): `border-input`, `bg-card`, `rounded-control`, focus `nq-focus`. The wrapper
is `display: contents`, so `className` only matters for layout contexts such as grid placement. Never override colours
with raw hex.

## Do / Don't

- **Do** store the ISO code; it is stable across languages.
- **Don't** use it for phone numbers; `PhoneInput` already has a country list.

## Related

- [AddressInput](https://docs.nasaqui.com/components/address-input) · [PhoneInput](https://docs.nasaqui.com/components/phone-input) · [CountryFlag](https://docs.nasaqui.com/components/country-flag) · [Combobox](https://docs.nasaqui.com/components/combobox)

## Lab

https://nasaq-ui.fadymondy.com/?path=/docs/components-forms-address-input--docs

## Code

### React

```tsx
import { CountrySelect, Field, FieldLabel } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Residence() {
  const [iso, setIso] = useState("");
  return (
    <Field>
      <FieldLabel>Country</FieldLabel>
      <CountrySelect value={iso} onValueChange={setIso} />
    </Field>
  );
}
```

### shadcn

```tsx
import { CountrySelect } from "@/components/ui/country-select";
import { Field, FieldLabel } from "@/components/ui/field";
import { useState } from "react";

export function Residence() {
  const [iso, setIso] = useState("");
  return (
    <Field>
      <FieldLabel>Country</FieldLabel>
      <CountrySelect value={iso} onValueChange={setIso} />
    </Field>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqCountrySelect } from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const country = ref("SA");
</script>

<template>
  <div class="w-72">
    <NqCountrySelect v-model="country" aria-label="Country" />
  </div>
</template>
```

### Blade

```blade
<div class="w-80">
    <x-nq::country-select name="country" value="SA" aria-label="Country" />
</div>
```

### HTML + Alpine

```html
<div class="w-80">
    <div data-slot="country-select" x-data="nqCountrySelect('SA', JSON.parse('{\u0022locale\u0022:\u0022en\u0022}'))" x-modelable="iso"
    class="contents">
    <div x-data="nqPlaceSelect({ options: () => items(), value: () => code(), set: (v) => pickCountry(v), disabled: () => false, empty: () => emptyText() })" class="contents">
        <div data-slot="combobox-input-group" x-ref="anchor"
    class="flex min-h-control min-w-0 w-full items-center gap-1 rounded-control border border-input bg-card text-body text-foreground transition-colors duration-150 ease-nq focus-within:border-nq-focus focus-within:outline-1 focus-within:outline-nq-focus has-[[data-invalid]]:border-nq-danger has-[[aria-invalid=true]]:border-nq-danger has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50 h-control ps-3 pe-1.5">
    <input data-slot="combobox-input" x-ref="input" x-bind="input"
                aria-label="Country" placeholder="Select a country" class="h-full min-w-0 flex-1 border-0 bg-transparent text-body text-foreground outline-none placeholder:text-muted-foreground pointer-coarse:text-[16px]">
            <button data-slot="combobox-clear" x-bind="clear" aria-label="Clear"
            class="flex size-6 shrink-0 cursor-default items-center justify-center rounded-control text-muted-foreground outline-none hover:text-foreground focus-visible:outline-1 focus-visible:outline-nq-focus [&_svg]:size-4 data-[hidden]:hidden"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
        <button data-slot="combobox-trigger" x-bind="trigger" aria-label="Open"
        class="flex size-6 shrink-0 cursor-default items-center justify-center rounded-control text-muted-foreground outline-none hover:text-foreground focus-visible:outline-1 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></button>
</div>
        <template x-teleport="body">
    <div data-slot="combobox-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.anchor"
        class="z-50 w-[var(--anchor-width)] max-h-[min(var(--available-height),20rem)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="combobox-empty" x-bind="emptyState" class="px-2.5 py-2 text-body-sm text-muted-foreground empty:hidden"></div>
            <div data-slot="combobox-list" class="outline-none">
                <template x-if="open"><div class="contents"><template x-for="o in matches()" :key="o.value">
                    <div data-slot="combobox-item" x-bind="item(o)"
                        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
                        <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
                            <span x-show="String(cfg.value()) === String(o.value)" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                        </span>
                        <span data-slot="combobox-item-text" class="min-w-0 flex-1 truncate">
                            <span class="flex items-center gap-2">
                                <span data-slot="country-flag" aria-hidden="true" x-data="nqCountryFlag(o.value)" x-html="svg"
                                    class="relative inline-block aspect-[3/2] h-[1em] shrink-0 overflow-hidden rounded-[2px] bg-muted align-[-0.125em] text-[1rem] [&>svg]:block [&>svg]:size-full after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-foreground/15 after:ring-inset"></span>
                                <span class="min-w-0 flex-1 truncate" x-text="o.label"></span>
                            </span>
                        </span>
                    </div>
                </template></div></template>
            </div>
    </div>
</template>
    </div>
            <input type="hidden" name="country" :value="code()">
    </div>
</div>
```
