# Select

A select lets people choose one option from a list, like a chain, a sort order or an asset class.

## When to use

- To choose one option from a known list of about 6 to 15 options, like a duration or an action type.
- To sort or filter a list, next to the list it changes.

## When not to use

- For two options, use a switch, or a segmented control when both should stay visible.
- For three to five options that fit on screen, use a segmented control or radio buttons, so people see every choice at once.
- For a long list or one people search, like tokens or wallets, use a combobox.
- For actions, like *Edit* or *Delete*, use a menu. A select chooses a value, it doesn't start anything.
- For a value people type, use an [Input](/components/input/).

## Anatomy

Wrap a field select in a `Field` to give it a label, a helper and an error. The field passes its id, description and error state to the trigger, as it does for an input.

The list opens 4px below the trigger and is at least as wide as it. It flips above the trigger when there isn't room below. Each row is as tall as the trigger, and the selected row shows a green check.

## Variants

- **Field** is the default, for forms and filters. It looks like an input: a white box with a neutral border.
- **Ghost** sits inline after a short label, like *Sort by*, where a boxed field would be too heavy. It has no border, and gets a light fill on hover.

## Sizes

Set the size on `Select`, so the trigger and its list match.

- **Medium** (40px) is the default, for forms.
- **Small** (32px) is for filters, table toolbars and inline sorting, next to small buttons and inputs.
- **Large** (48px) is for the main choice in a panel.

A select and the inputs or buttons beside it should be the same size.

## Options

### Groups

Group options under a label when the list has clear kinds, like actions on tokens and actions on the project. Group every option or none, and never make a group of one. A separator sits between groups.

A disabled option stays in the list at 40% opacity. Say why with a description, like *Not available yet*, so people don't wonder where it went.

### Logos and descriptions

Put a chain or token logo before the label when people scan for it by sight. The logo shows in the trigger too. A description adds one line under the label, in the list only, for options that need explaining, like order types.

### Long and empty lists

The list scrolls after about six and a half rows, so the cut-off row shows there's more. When there is nothing to choose, show `SelectEmpty` with the reason, like *No wallets connected*.

## States

The field doesn't change on hover. On focus the border turns green with a soft 3px halo, as on an input. While the list is open the border darkens and the chevron turns up. The row under the pointer or keyboard gets a light fill (`surface/accent`).

### Helper and error

A helper sits below the field. An error replaces it and turns the border red. Show an error when people try to submit without choosing.

### Disabled

A disabled select is 40% opacity and can't be opened. Say why next to it.

## Content

- Write the label as a noun, in sentence case and without a colon: *Chain*, *Asset class*, *Sort bids*.
- Write the placeholder as *Select a [noun]*, like *Select a chain*. Use a default value instead when one choice is right for most people.
- Keep options short and in sentence case. Order them logically, like shortest to longest or most used first, and alphabetically only when there is no natural order.

## Behaviour

| Key | Action |
| --- | --- |
| <kbd>Space</kbd>, <kbd>Enter</kbd>, <kbd>↓</kbd> | Opens the list with focus on the selected option. |
| <kbd>↑</kbd> <kbd>↓</kbd> | Moves between options, skipping disabled ones. |
| <kbd>Home</kbd> <kbd>End</kbd> | Jumps to the first or last option. |
| Typing | Jumps to the option that starts with the typed letters. |
| <kbd>Enter</kbd> | Chooses the focused option and closes the list. |
| <kbd>Esc</kbd> | Closes the list without changing the value, and returns focus to the trigger. |

Clicking outside closes the list. While it's open, the page behind doesn't scroll.

## Accessibility

- Every select needs a label. Use `Field`, or an `aria-label` on the trigger when a visible text beside it already says what it is, like *Sort by*.
- The trigger is a `combobox` and the list a `listbox`, so screen readers announce the label and the current value.
- `Field` links the helper or error with `aria-describedby` and sets `aria-invalid` on an error.
- The selected option is announced as selected, so the green check is never the only sign.
- Focus shows a green border and halo on a field, and a 2px ring on ghost. In Windows high contrast mode the field falls back to an outline.

## Props

### Select

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `size` | `"sm"` \| `"md"` \| `"lg"` | `"md"` | Sets the trigger and its list: 32, 40 or 48px. |
| `value` | `string` | | The chosen value, when you control it. Use `defaultValue` otherwise. |
| `onValueChange` | `(value: string) => void` | | Called with the new value. |
| `disabled` | `boolean` | `false` | |

Every other Radix `Select.Root` prop passes through, like `open`, `onOpenChange` and `name`.

### SelectTrigger

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `variant` | `"field"` \| `"ghost"` | `"field"` | Field fills its container; ghost fits its value. |
| `className` | `string` | | Usually a width, like `w-[180px]`. |

Put a `SelectValue` inside, with a `placeholder` when there is no default.

### SelectContent

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `align` | `"start"` \| `"center"` \| `"end"` | `"start"` | Lines the list up with the trigger. |
| `sideOffset` | `number` | `4` | Gap below the trigger, in px. |
| `position` | `"popper"` \| `"item-aligned"` | `"popper"` | Item-aligned places the selected option over the trigger, as on macOS. |
| `container` | `HTMLElement` | `document.body` | Where the list renders. |

### SelectItem

| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `value` | `string` | | Required. |
| `description` | `ReactNode` | | One line under the label, in the list only. |
| `disabled` | `boolean` | `false` | Keep it in the list and say why in `description`. |

`SelectGroup`, `SelectLabel`, `SelectSeparator` and `SelectEmpty` take no props of their own.

## Related

- An [Input](/components/input/) is for values people type. A select and an input in one row should be the same size.
- A [Dialog](/components/dialog/) often holds a select, like the token in a withdrawal.
