# Select

A control that opens a list of options and takes one value.

```tsx
import { Field, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

export default function SelectHero() {
  return (
    <Field label="Currency" hint="New invoices use this currency." className="w-full max-w-xs">
      {(control) => (
        <Select defaultValue="eur">
          <SelectTrigger {...control} className="w-full">
            <SelectValue placeholder="Select a currency" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="usd">US dollar</SelectItem>
            <SelectItem value="eur">Euro</SelectItem>
            <SelectItem value="gbp">Pound sterling</SelectItem>
            <SelectItem value="jpy">Japanese yen</SelectItem>
            <SelectItem value="chf">Swiss franc</SelectItem>
          </SelectContent>
        </Select>
      )}
    </Field>
  )
}
```

## Usage guidelines

- **One value among 4 to 15 options.** Use a select for a short list that you know: a language, a role, a currency.
- **Values, not actions.** For a list of actions, use [Dropdown Menu](/docs/components/dropdown-menu).
- **Give the trigger a name.** Use [Field](/docs/components/field), a [Label](/docs/components/label) or `aria-label`.
- **No empty value.** The `value` of a `SelectItem` cannot be an empty string. Use a value such as `"none"`.

| Choice | Control |
| --- | --- |
| One value among 4 to 15 options | `Select` |
| One value among 2 to 5 options | [Radio Group](/docs/components/radio-group) |
| One value among 2 to 6 options with a description or an icon | [Radio Card Group](/docs/components/radio-card-group) |
| One value in a long list, or a list with a search field | [Command](/docs/components/command) in a [Popover](/docs/components/popover) |

## Anatomy

Import the parts and put them together.

```tsx title="Anatomy"

  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from 'ferry-ui'

<Select>
  <SelectTrigger>
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel />
      <SelectItem />
    </SelectGroup>
    <SelectSeparator />
    <SelectItem />
  </SelectContent>
</Select>
```

| Part | Role |
| --- | --- |
| `Select` | Holds the value and the open state. |
| `SelectTrigger` | The button that opens the list. |
| `SelectValue` | Shows the text of the selected option, or the placeholder. |
| `SelectContent` | The overlay that holds the options. |
| `SelectItem` | One option. |
| `SelectGroup` | A set of options below a `SelectLabel`. |
| `SelectLabel` | The heading of a group. It is not an option. |
| `SelectSeparator` | A line between two groups. |
| `SelectScrollUpButton`, `SelectScrollDownButton` | The scroll buttons of a long list. `SelectContent` adds them for you. |

## Examples

### Label and width

In a `Field`, pass a function as the child. The function gets the props of the control. Spread them on `SelectTrigger`.

To link a `Label` yourself, pass the same value to `htmlFor` and to the `id` of `SelectTrigger`.

The trigger is as wide as its content. In a form, add `className="w-full"`.

```tsx
import { Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

export default function SelectLabelFor() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <Label htmlFor="member-role">Role</Label>
      <Select defaultValue="member">
        <SelectTrigger id="member-role" className="w-full">
          <SelectValue placeholder="Select a role" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="viewer">Viewer</SelectItem>
          <SelectItem value="member">Member</SelectItem>
          <SelectItem value="billing">Billing manager</SelectItem>
          <SelectItem value="admin">Admin</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Sizes

The `size` prop of `SelectTrigger` sets the height. The three heights are the same as for [Input](/docs/components/input) and [Button](/docs/components/button). There is no `lg` size.

```tsx
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

const SIZES = ['tiny', 'sm', 'md'] as const

export default function SelectSizes() {
  return (
    <>
      {SIZES.map((size) => (
        <Select key={size} defaultValue="30d">
          <SelectTrigger size={size} aria-label={`Date range, size ${size}`}>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="24h">Last 24 hours</SelectItem>
            <SelectItem value="7d">Last 7 days</SelectItem>
            <SelectItem value="30d">Last 30 days</SelectItem>
            <SelectItem value="90d">Last 90 days</SelectItem>
          </SelectContent>
        </Select>
      ))}
    </>
  )
}
```

| Size | Height | Use |
| --- | --- | --- |
| `tiny` | 26px | Toolbars. |
| `sm` | 30px | Filters and dense forms. |
| `md` | 34px | Form fields. It is the default. |

### Groups

`SelectGroup` and `SelectLabel` give a heading to a set of options. `SelectSeparator` adds a line between two groups.

```tsx
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from 'ferry-ui'

export default function SelectGroups() {
  return (
    <Select defaultValue="europe-paris">
      <SelectTrigger className="w-64" aria-label="Time zone">
        <SelectValue placeholder="Select a time zone" />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          <SelectLabel>Americas</SelectLabel>
          <SelectItem value="america-new-york">New York (UTC−05:00)</SelectItem>
          <SelectItem value="america-chicago">Chicago (UTC−06:00)</SelectItem>
          <SelectItem value="america-los-angeles">Los Angeles (UTC−08:00)</SelectItem>
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>Europe</SelectLabel>
          <SelectItem value="europe-london">London (UTC+00:00)</SelectItem>
          <SelectItem value="europe-paris">Paris (UTC+01:00)</SelectItem>
          <SelectItem value="europe-berlin">Berlin (UTC+01:00)</SelectItem>
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>Asia Pacific</SelectLabel>
          <SelectItem value="asia-tokyo">Tokyo (UTC+09:00)</SelectItem>
          <SelectItem value="australia-sydney">Sydney (UTC+10:00)</SelectItem>
        </SelectGroup>
      </SelectContent>
    </Select>
  )
}
```

### Placeholder and controlled value

`SelectValue` shows its `placeholder` when the select has no value.

A select holds its value by default. To control the value, pass `value` and `onValueChange`. An empty `value` shows the placeholder again.

```tsx
import * as React from 'react'
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

export default function SelectControlled() {
  const [language, setLanguage] = React.useState('')

  return (
    <>
      <Select value={language} onValueChange={setLanguage}>
        <SelectTrigger className="w-48" aria-label="Language">
          <SelectValue placeholder="Select a language" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="en">English</SelectItem>
          <SelectItem value="fr">French</SelectItem>
          <SelectItem value="de">German</SelectItem>
          <SelectItem value="es">Spanish</SelectItem>
          <SelectItem value="ja">Japanese</SelectItem>
        </SelectContent>
      </Select>
      {/* An empty value shows the placeholder again. */}
      <Button variant="ghost" disabled={language === ''} onClick={() => setLanguage('')}>
        Clear
      </Button>
    </>
  )
}
```

### Position

By default, the list covers the trigger. With `position="popper"`, the list opens below the trigger. It is then as wide as the trigger or wider. It also follows `side`, `align` and `sideOffset`.

```tsx
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

export default function SelectPopper() {
  return (
    <Select defaultValue="fr">
      <SelectTrigger className="w-48" aria-label="Language">
        <SelectValue />
      </SelectTrigger>
      <SelectContent position="popper">
        <SelectItem value="en">English</SelectItem>
        <SelectItem value="fr">French</SelectItem>
        <SelectItem value="de">German</SelectItem>
        <SelectItem value="es">Spanish</SelectItem>
        <SelectItem value="ja">Japanese</SelectItem>
      </SelectContent>
    </Select>
  )
}
```

### Icons

An option can start with an icon. `SelectItem` sets the size and the color of the icon. The trigger shows the icon of the selected option.

```tsx
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'
import { Banknote, CreditCard, Landmark, Wallet } from 'lucide-react'

export default function SelectIcons() {
  return (
    <Select defaultValue="card">
      <SelectTrigger className="w-56" aria-label="Payment method">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="card">
          <CreditCard />
          Credit card
        </SelectItem>
        <SelectItem value="transfer">
          <Landmark />
          Bank transfer
        </SelectItem>
        <SelectItem value="wallet">
          <Wallet />
          Digital wallet
        </SelectItem>
        <SelectItem value="cash">
          <Banknote />
          Cash
        </SelectItem>
      </SelectContent>
    </Select>
  )
}
```

### Disabled state

`disabled` on `Select` disables the trigger. `disabled` on a `SelectItem` disables one option.

```tsx
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'ferry-ui'

export default function SelectDisabled() {
  return (
    <>
      <Select disabled defaultValue="eur">
        <SelectTrigger className="w-40" aria-label="Currency">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="usd">US dollar</SelectItem>
          <SelectItem value="eur">Euro</SelectItem>
          <SelectItem value="gbp">Pound sterling</SelectItem>
        </SelectContent>
      </Select>
      <Select defaultValue="member">
        <SelectTrigger className="w-40" aria-label="Role">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="viewer">Viewer</SelectItem>
          <SelectItem value="member">Member</SelectItem>
          <SelectItem value="admin">Admin</SelectItem>
          <SelectItem value="owner" disabled>
            Owner, by transfer only
          </SelectItem>
        </SelectContent>
      </Select>
    </>
  )
}
```

### Error

`aria-invalid` on `SelectTrigger` shows the error border. `Field` sets it when you pass `error`.

```tsx
import * as React from 'react'
import { Button, Field, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast } from 'ferry-ui'

export default function SelectInvalid() {
  const [role, setRole] = React.useState('')
  const [error, setError] = React.useState<string>()

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setError(role === '' ? 'Select a role for this member.' : undefined)
    if (role !== '') toast.success('Invitation sent')
  }

  function change(value: string) {
    setRole(value)
    setError(undefined)
  }

  return (
    <form noValidate onSubmit={submit} className="flex w-full max-w-xs flex-col items-start gap-4">
      <Field label="Role" error={error} className="w-full">
        {(control) => (
          <Select value={role} onValueChange={change}>
            <SelectTrigger {...control} className="w-full">
              <SelectValue placeholder="Select a role" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="viewer">Viewer</SelectItem>
              <SelectItem value="member">Member</SelectItem>
              <SelectItem value="billing">Billing manager</SelectItem>
              <SelectItem value="admin">Admin</SelectItem>
            </SelectContent>
          </Select>
        )}
      </Field>
      <Button type="submit">Send invitation</Button>
    </form>
  )
}
```

## Accessibility

- <Kbd>Enter</Kbd> or <Kbd>↓</Kbd> opens the list. The arrow keys move the focus between the options.
- <Kbd>Enter</Kbd> selects the option that has the focus. <Kbd>Esc</Kbd> closes the list.
- The trigger must have an accessible name.

## API reference

Each part also accepts the props of its Radix UI primitive and the attributes of its element.

### Select

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `open` | `boolean` |  |  |
| `defaultOpen` | `boolean` |  |  |
| `onOpenChange` | `((open: boolean) => void)` |  |  |
| `dir` | `"ltr" \| "rtl"` |  |  |
| `name` | `string` |  |  |
| `autoComplete` | `string` |  |  |
| `disabled` | `boolean` |  |  |
| `required` | `boolean` |  |  |
| `form` | `string` |  |  |
| `value` | `string` |  |  |
| `defaultValue` | `string` |  |  |
| `onValueChange` | `((value: string) => void)` |  |  |

### SelectTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "tiny" \| "sm" \| "md"` | `md` | Height, on the `Input` / `Button` scale: `tiny` 26px (toolbars), `sm` 30px (filters, dense forms), `md` 34px (default: form fields). `default` is a deprecated alias of `md`. |
| `asChild` | `boolean` |  |  |

### SelectValue

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `ReactNode` |  |  |
| `asChild` | `boolean` |  |  |

### SelectContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `onCloseAutoFocus` | `((event: Event) => void)` |  | Event handler called when auto-focusing on close. Can be prevented. |
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` |  | Event handler called when the escape key is down. Can be prevented. |
| `onPointerDownOutside` | `((event: PointerDownOutsideEvent) => void)` |  | Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
| `position` | `"item-aligned" \| "popper"` | `item-aligned` |  |
| `asChild` | `boolean` |  |  |
| `side` | `"top" \| "right" \| "bottom" \| "left"` |  |  |
| `sideOffset` | `number` |  |  |
| `align` | `"center" \| "start" \| "end"` | `center` |  |
| `alignOffset` | `number` |  |  |
| `arrowPadding` | `number` |  |  |
| `avoidCollisions` | `boolean` |  |  |
| `collisionBoundary` | `Boundary \| Boundary[]` |  |  |
| `collisionPadding` | `number \| Partial<Record<"top" \| "right" \| "bottom" \| "left", number>>` |  |  |
| `sticky` | `"partial" \| "always"` |  |  |
| `hideWhenDetached` | `boolean` |  |  |
| `updatePositionStrategy` | `"always" \| "optimized"` |  |  |

### SelectItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  |  |
| `disabled` | `boolean` |  |  |
| `textValue` | `string` |  |  |
| `asChild` | `boolean` |  |  |

### SelectGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### SelectLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### SelectSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### SelectScrollUpButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### SelectScrollDownButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
