# Select (/ui/components/react/select)



<Playground component="select" />

## Usage [#usage]

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'
```

```tsx
<Select>
  <SelectTrigger>
    <SelectValue placeholder="Pick a fruit" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="apple">Apple</SelectItem>
    <SelectItem value="orange">Orange</SelectItem>
  </SelectContent>
</Select>
```

`Select` is a compound component. The root holds the value and shared options (`size`, `variant`, `multiple`, `alignItemWithTrigger`); the parts compose the trigger and the portalled popup:

* **`SelectTrigger`** — the button styled like [`Input`](/ui/components/react/input). Add a clear button with `clearable`, and adornments with `startSlot` / `endSlot`.
* **`SelectValue`** — renders the current selection inside the trigger. Pass a `placeholder`, or a function child to format the value (e.g. for multi-select).
* **`SelectContent`** — the portalled, auto-positioned popup. Accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).
* **`SelectItem`** — one option; its `value` is what selection reports.
* **`SelectGroup`*&#x2A; / &#x2A;*`SelectGroupLabel`*&#x2A; / &#x2A;*`SelectSeparator`** — optional structure for grouped lists.

Reach for a `Select` when the options are short and known. For a long list users need to **filter by typing**, use a [`Combobox`](/ui/components/react/combobox) or [`Autocomplete`](/ui/components/react/autocomplete) instead.

## Examples [#examples]

### Variants [#variants]

`variant` switches the trigger appearance between a bordered `outline` (default) and a filled `soft` field.

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Orange', 'Strawberry']
const variants = ['outline', 'soft'] as const

export default function SelectVariants() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      {variants.map((variant) => (
        <Select key={variant} variant={variant}>
          <SelectTrigger aria-label={`Pick a fruit (${variant})`}>
            <SelectValue placeholder="Pick a fruit" />
          </SelectTrigger>
          <SelectContent>
            {fruits.map((fruit) => (
              <SelectItem key={fruit} value={fruit}>
                {fruit}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      ))}
    </div>
  )
}
```

### Sizes [#sizes]

`size` on the root scales the trigger, popup radius, and items together — `sm`, `md` (default), or `lg`.

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Orange', 'Strawberry']
const sizes = ['sm', 'md', 'lg'] as const

export default function SelectSizes() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      {sizes.map((size) => (
        <Select key={size} size={size}>
          <SelectTrigger aria-label={`Pick a fruit (${size})`}>
            <SelectValue placeholder="Pick a fruit" />
          </SelectTrigger>
          <SelectContent>
            {fruits.map((fruit) => (
              <SelectItem key={fruit} value={fruit}>
                {fruit}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      ))}
    </div>
  )
}
```

### Grouped options [#grouped-options]

Wrap related items in `SelectGroup` with a `SelectGroupLabel`, and divide groups with `SelectSeparator`. Mark an option `disabled` to make it unselectable.

```tsx
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectSeparator,
} from '@appica/ui-react/select'

export default function SelectGrouped() {
  return (
    <div className="w-full max-w-70">
      <Select>
        <SelectTrigger aria-label="Pick a food">
          <SelectValue placeholder="Pick a food" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectGroupLabel>Fruits</SelectGroupLabel>
            <SelectItem value="Apple">Apple</SelectItem>
            <SelectItem value="Banana">Banana</SelectItem>
            <SelectItem value="Orange">Orange</SelectItem>
            <SelectItem value="Strawberry">Strawberry</SelectItem>
          </SelectGroup>
          <SelectSeparator />
          <SelectGroup>
            <SelectGroupLabel>Vegetables</SelectGroupLabel>
            <SelectItem value="Carrot">Carrot</SelectItem>
            <SelectItem value="Potato">Potato</SelectItem>
            <SelectItem value="Tomato">Tomato</SelectItem>
            <SelectItem value="Broccoli" disabled>
              Broccoli
            </SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Clearable [#clearable]

Set `clearable` on the trigger to add a clear (✕) button that resets the selection once a value is present.

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Orange', 'Strawberry']

export default function SelectClearable() {
  return (
    <div className="w-full max-w-70">
      <Select defaultValue="Orange">
        <SelectTrigger clearable aria-label="Pick a fruit">
          <SelectValue placeholder="Pick a fruit" />
        </SelectTrigger>
        <SelectContent>
          {fruits.map((fruit) => (
            <SelectItem key={fruit} value={fruit}>
              {fruit}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Disabled & error states [#disabled--error-states]

`disabled` on the root greys out the trigger and blocks interaction. Set `aria-invalid` on the `SelectTrigger` to paint the error state — it's mirrored to `data-invalid`. Inside a [`Field`](/ui/components/react/field), the trigger also inherits the field's `disabled` and `invalid` state, which is the better choice when you're rendering a validation message.

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Orange', 'Strawberry']

export default function SelectStates() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Select disabled>
        <SelectTrigger aria-label="Disabled">
          <SelectValue placeholder="Disabled" />
        </SelectTrigger>
        <SelectContent>
          {fruits.map((fruit) => (
            <SelectItem key={fruit} value={fruit}>
              {fruit}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>

      <Select defaultValue="Orange">
        <SelectTrigger aria-invalid aria-label="With error">
          <SelectValue placeholder="Pick a fruit" />
        </SelectTrigger>
        <SelectContent>
          {fruits.map((fruit) => (
            <SelectItem key={fruit} value={fruit}>
              {fruit}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Start & end slots [#start--end-slots]

`SelectTrigger` accepts `startSlot` and `endSlot` for adornments — an icon, a badge, a unit — rendered inside the trigger frame, around the value.

```tsx
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'
import { CreditCard } from '@appica/icons-react'

const methods = ['Credit card', 'PayPal', 'Bank transfer', 'Apple Pay', 'Google Pay']

export default function SelectSlots() {
  return (
    <div className="w-full max-w-70">
      <Select defaultValue="Credit card" alignItemWithTrigger={false}>
        <SelectTrigger
          aria-label="Payment method"
          startSlot={<CreditCard />}
          endSlot={<span className="text-foreground-subtle text-xs">Default</span>}
        >
          <SelectValue placeholder="Payment method" />
        </SelectTrigger>
        <SelectContent>
          {methods.map((method) => (
            <SelectItem key={method} value={method}>
              {method}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

<Callout type="warning" title="Disable alignItemWithTrigger when using a startSlot">
  By default `alignItemWithTrigger` overlays the selected item directly on top of the trigger when the popup opens (an
  iOS-style picker). That alignment assumes the value starts at the trigger's leading edge, so a `startSlot` throws the
  popup's horizontal position off. Whenever you add a `startSlot`, set `alignItemWithTrigger={false}` on the `Select` so
  the popup anchors below the trigger instead.
</Callout>

### Country select [#country-select]

A compact country picker: each item shows a [flag](/ui/country-flags) before the name, and the selected country's flag is mirrored into the trigger through `startSlot`. Because of that slot, the root sets `alignItemWithTrigger={false}`. For a long, searchable country list, use a [`Combobox`](/ui/components/react/combobox) instead.

```tsx
'use client'

import * as React from 'react'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'
import { CountryFlagRounded } from '@appica/country-flags-react'

interface Country {
  code: string
  name: string
}

const countries: Country[] = [
  { code: 'au', name: 'Australia' },
  { code: 'br', name: 'Brazil' },
  { code: 'ca', name: 'Canada' },
  { code: 'fr', name: 'France' },
  { code: 'de', name: 'Germany' },
  { code: 'jp', name: 'Japan' },
  { code: 'gb', name: 'United Kingdom' },
  { code: 'us', name: 'United States' },
]

export default function SelectCountry() {
  const [value, setValue] = React.useState<string | null>('us')
  const selected = countries.find((country) => country.code === value)

  return (
    <div className="w-full max-w-70">
      <Select value={value} onValueChange={(v) => setValue(v as string | null)} alignItemWithTrigger={false}>
        <SelectTrigger
          aria-label="Select a country"
          startSlot={selected ? <CountryFlagRounded code={selected.code} aria-hidden /> : null}
        >
          <SelectValue placeholder="Select a country">{() => selected?.name}</SelectValue>
        </SelectTrigger>
        <SelectContent>
          {countries.map((country) => (
            <SelectItem key={country.code} value={country.code}>
              <CountryFlagRounded code={country.code} data-icon="start" aria-hidden />
              {country.name}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### User select [#user-select]

Rich items with an [`Avatar`](/ui/components/react/avatar), a name, and a trailing role — a typical "assign to" picker. The chosen person's avatar is echoed into the trigger via `startSlot` (so `alignItemWithTrigger` is off here too).

```tsx
'use client'

import * as React from 'react'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'
import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar'

interface User {
  id: string
  name: string
  avatar: string
  initials: string
}

const users: User[] = [
  { id: 'sarah', name: 'Sarah Jenkins', avatar: '/avatars/01.jpg', initials: 'SJ' },
  { id: 'liam', name: 'Liam Hudson', avatar: '/avatars/02.jpg', initials: 'LH' },
  { id: 'mateo', name: 'Mateo Rossi', avatar: '/avatars/03.jpg', initials: 'MR' },
  { id: 'ava', name: 'Ava Thompson', avatar: '/avatars/04.jpg', initials: 'AT' },
  { id: 'emma', name: 'Emma Garcia', avatar: '/avatars/09.jpg', initials: 'EG' },
  { id: 'noah', name: 'Noah Patel', avatar: '/avatars/06.jpg', initials: 'NP' },
]

export default function SelectUsers() {
  const [value, setValue] = React.useState<string | null>('liam')
  const selected = users.find((user) => user.id === value)

  return (
    <div className="w-full max-w-70">
      <Select value={value} onValueChange={(v) => setValue(v as string | null)} alignItemWithTrigger={false}>
        <SelectTrigger
          aria-label="Assign to"
          startSlot={
            selected ? (
              <Avatar size={22}>
                <AvatarImage src={selected.avatar} alt={selected.name} />
                <AvatarFallback>{selected.initials}</AvatarFallback>
              </Avatar>
            ) : null
          }
        >
          <SelectValue placeholder="Assign to">{() => selected?.name}</SelectValue>
        </SelectTrigger>
        <SelectContent>
          {users.map((user) => (
            <SelectItem key={user.id} value={user.id}>
              <Avatar size={22} data-icon="start">
                <AvatarImage src={user.avatar} alt={user.name} />
                <AvatarFallback>{user.initials}</AvatarFallback>
              </Avatar>
              {user.name}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Multiple selection [#multiple-selection]

Set `multiple` on the root and the value becomes an array. Selected items keep a check, and a function child on `SelectValue` formats the trigger summary.

```tsx
'use client'

import * as React from 'react'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const toppings = ['Pepperoni', 'Mushrooms', 'Onions', 'Olives', 'Peppers', 'Bacon', 'Pineapple']

export default function SelectMultiple() {
  const [value, setValue] = React.useState<string[]>(['Pepperoni', 'Mushrooms'])

  return (
    <div className="w-full max-w-70">
      <Select multiple value={value} onValueChange={(v) => setValue(v as string[])}>
        <SelectTrigger clearable aria-label="Choose toppings">
          <SelectValue placeholder="Choose toppings">
            {(selected: string[]) =>
              selected.length === 0
                ? 'Choose toppings'
                : selected.length === 1
                  ? selected[0]
                  : `${selected.length} toppings`
            }
          </SelectValue>
        </SelectTrigger>
        <SelectContent>
          {toppings.map((topping) => (
            <SelectItem key={topping} value={topping}>
              {topping}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Controlled [#controlled]

Drive the selection yourself with `value` + `onValueChange` — useful for syncing with form state or reacting to every change. Use `null` (or `[]` when `multiple`) for the empty value.

```tsx
'use client'

import * as React from 'react'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@appica/ui-react/select'

const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Orange', 'Strawberry']

export default function SelectControlled() {
  const [value, setValue] = React.useState<string | null>(null)

  return (
    <div className="flex w-full max-w-70 flex-col gap-3">
      <Select value={value} onValueChange={(v) => setValue(v as string | null)}>
        <SelectTrigger clearable aria-label="Pick a fruit">
          <SelectValue placeholder="Pick a fruit" />
        </SelectTrigger>
        <SelectContent>
          {fruits.map((fruit) => (
            <SelectItem key={fruit} value={fruit}>
              {fruit}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
      <p className="text-foreground-muted text-sm">
        Current value: <span className="text-foreground font-medium">{value ?? '—'}</span>
      </p>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The trigger, popup, and check indicators all mirror to follow the resolved direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="select" />

## API reference [#api-reference]

`Select` wraps [Base UI's Select](https://base-ui.com/react/components/select). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### Select [#select]

The root. Holds the value and shared styling/behavior options; renders no DOM of its own.

| Prop                   | <ColMinWidth width="180">Type</ColMinWidth>                       | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                                 |
| ---------------------- | ----------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `size`                 | <Code>'sm' \| 'md' \| 'lg'</Code>                                 | `'md'`      | Trigger height, popup radius, and item sizing.                                                     |
| `variant`              | <Code>'outline' \| 'soft'</Code>                                  | `'outline'` | Trigger appearance — bordered or filled.                                                           |
| `alignItemWithTrigger` | `boolean`                                                         | `true`      | Overlay the selected item over the trigger on open. &#x2A;*Set `false` when using a `startSlot`.** |
| `multiple`             | `boolean`                                                         | `false`     | Allow selecting several values; the value becomes an array.                                        |
| `value`                | <Code>Value \| Value\[] \| null</Code>                            | —           | Controlled selection. Pair with `onValueChange`.                                                   |
| `defaultValue`         | <Code>Value \| Value\[] \| null</Code>                            | —           | Uncontrolled initial selection.                                                                    |
| `onValueChange`        | <Code>(value, details) => void</Code>                             | —           | Fires when the selection changes.                                                                  |
| `open`                 | `boolean`                                                         | —           | Controlled open state of the popup. Pair with `onOpenChange`.                                      |
| `defaultOpen`          | `boolean`                                                         | `false`     | Uncontrolled initial open state.                                                                   |
| `onOpenChange`         | <Code>(open, details) => void</Code>                              | —           | Fires when the popup opens or closes.                                                              |
| `items`                | <Code>Record\<string, ReactNode> \| {'{ value, label }[]'}</Code> | —           | Value→label map so `SelectValue` can render a label without a function child.                      |
| `name`                 | `string`                                                          | —           | Field name submitted with a form.                                                                  |
| `disabled`             | `boolean`                                                         | `false`     | Disable the whole control.                                                                         |
| `required`             | `boolean`                                                         | `false`     | Require a value before the form submits.                                                           |
| `readOnly`             | `boolean`                                                         | `false`     | Focusable but not changeable.                                                                      |
| `modal`                | `boolean`                                                         | `true`      | Whether the popup traps focus and blocks outside interaction.                                      |

### SelectTrigger [#selecttrigger]

The button that opens the popup, styled like [`Input`](/ui/components/react/input).

| Prop           | <ColMinWidth width="160">Type</ColMinWidth> | Default | Description                                                           |
| -------------- | ------------------------------------------- | ------- | --------------------------------------------------------------------- |
| `clearable`    | `boolean`                                   | `false` | Render a clear button inside the trigger when a value is present.     |
| `startSlot`    | `ReactNode`                                 | —       | Adornment before the value. Pair with `alignItemWithTrigger={false}`. |
| `endSlot`      | `ReactNode`                                 | —       | Adornment after the value, before the chevron.                        |
| `aria-invalid` | <Code>boolean \| 'true' \| 'false'</Code>   | —       | Marks the error state; mirrored to `data-invalid` for styling.        |
| `className`    | `string`                                    | —       | Extra classes on the trigger, merged via `tailwind-merge`.            |

Also forwards every native `<button>` prop (`onClick`, `aria-*`, `ref`, …).

### SelectValue [#selectvalue]

Renders the current selection inside the trigger.

| Prop          | <ColMinWidth width="200">Type</ColMinWidth>    | Default | Description                                                  |
| ------------- | ---------------------------------------------- | ------- | ------------------------------------------------------------ |
| `placeholder` | `string`                                       | —       | Shown when nothing is selected.                              |
| `children`    | <Code>ReactNode \| (value) => ReactNode</Code> | —       | A function child formats the value — handy for multi-select. |

### SelectContent [#selectcontent]

The portalled, auto-positioned popup wrapping the list. Accepts the shared floating props.

| Prop                   | <ColMinWidth width="160">Type</ColMinWidth>         | Default    | Description                                                     |
| ---------------------- | --------------------------------------------------- | ---------- | --------------------------------------------------------------- |
| `alignItemWithTrigger` | `boolean`                                           | —          | Per-popup override of the root's alignment setting.             |
| `side`                 | <Code>'top' \| 'right' \| 'bottom' \| 'left'</Code> | `'bottom'` | Preferred side to open on (when not aligned with the trigger).  |
| `align`                | <Code>'start' \| 'center' \| 'end'</Code>           | —          | Alignment along that side.                                      |
| `sideOffset`           | `number`                                            | `6`        | Gap between the trigger and the popup.                          |
| `alignOffset`          | `number`                                            | —          | Shift along the alignment axis.                                 |
| `container`            | <Code>HTMLElement \| ref \| null</Code>             | —          | Portal target — render the popup into a different node.         |
| `keepMounted`          | `boolean`                                           | `false`    | Keep the popup mounted in the DOM while closed.                 |
| `positionerProps`      | `object`                                            | —          | Escape hatch for the positioner element (`style`, `render`, …). |
| `portalProps`          | `object`                                            | —          | Escape hatch for the portal element (`style`, `render`, …).     |
| `className`            | `string`                                            | —          | Extra classes on the popup.                                     |

### SelectItem [#selectitem]

One option in the list.

| Prop        | Type      | Default | Description                                             |
| ----------- | --------- | ------- | ------------------------------------------------------- |
| `value`     | `T`       | —       | The item this option represents; reported on selection. |
| `disabled`  | `boolean` | `false` | Make the option unselectable.                           |
| `className` | `string`  | —       | Extra classes, merged via `tailwind-merge`.             |

### SelectGroup [#selectgroup]

Wraps one labelled section of the list.

| Prop        | Type        | Default | Description                                  |
| ----------- | ----------- | ------- | -------------------------------------------- |
| `children`  | `ReactNode` | —       | A `SelectGroupLabel` plus its `SelectItem`s. |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`.  |

### SelectGroupLabel [#selectgrouplabel]

The visible label for a `SelectGroup`.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The label text.                             |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### SelectSeparator [#selectseparator]

A thin divider between groups.

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

## Accessibility [#accessibility]

* The trigger has `role="combobox"`; the popup is a `listbox` and items are `option`s. Selection and active state are wired through `aria-activedescendant` and `aria-selected`.
* **Space / Enter / ↓*&#x2A; open the popup, &#x2A;*↑/↓** move the highlight, **Enter** selects, **Escape** closes, and typing a letter jumps to the matching option (typeahead).
* Give the trigger an accessible name — an adjacent `<label>`, `aria-label`, or `aria-labelledby`.
* When `clearable` and a value is present, the trigger clears on **Delete** / **Backspace** (keeping focus on the trigger); the pointer ✕ affordance is decorative (`aria-hidden`).
* `disabled` removes the control from the tab order; `readOnly` keeps it focusable but inert.
* `aria-invalid` on the trigger is bridged to `data-invalid` so the error state styles with or without a wrapping `Field`.
* Popup enter/exit transitions are skipped when the user prefers reduced motion.
