# Select

A styled select: a full-width field trigger, or a compact inline trigger that sits within a sentence, with a popup list.

- Category: Forms
- Page: https://wunderui.com/components/select
- Tier: Free

## Install

```bash
npx wunderui-cli add select
```

## Import

```tsx
import { Select, InlineSelect, InlineSelectContent, InlineSelectItem, InlineSelectTrigger, InlineSelectValue, NativeSelect, NativeSelectContent, NativeSelectItem, NativeSelectTrigger, NativeSelectValue, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
```

## Minimal usage

Then use it:

```tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function StatusField() {
  return (
    <Select>
      <SelectTrigger aria-label="Status">
        <SelectValue placeholder="Select a status" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="backlog">Backlog</SelectItem>
        <SelectItem value="in-progress">In progress</SelectItem>
        <SelectItem value="done">Done</SelectItem>
      </SelectContent>
    </Select>
  )
}
```

Expected result: You see a field with the placeholder Select a status and a chevron. A click opens three options below it; choosing In progress closes the list and puts the label in the field.

## Usage

```tsx
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="status">Status</Label>
      <Select>
        <SelectTrigger id="status">
          <SelectValue placeholder="Select a status" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="backlog">Backlog</SelectItem>
          <SelectItem value="todo">To do</SelectItem>
          <SelectItem value="in-progress">In progress</SelectItem>
          <SelectItem value="in-review">In review</SelectItem>
          <SelectItem value="done">Done</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

## When to use

### Use Select for

- Choosing one option from a short, fixed list, such as a status, a role or a sort order
- A form field that shows the chosen option in the field itself
- Lists of about 3 to 12 options that people know by sight
- Inline (`SelectTrigger variant="inline"`): a value inside a sentence, such as Show comments from the last 30 days
- Inline: a compact choice in a toolbar, a card title or a table row, such as a sort order, a period or a status

### Use something else

- [Combobox](https://wunderui.com/components/combobox) for a long list people need to search
- [Radio Group](https://wunderui.com/components/radio-group) for two to four options that should all be visible
- [Dropdown Menu](https://wunderui.com/components/dropdown-menu) for actions, not a value for a field

## Keyboard

The field is a native button, so the browser handles focus. Once the list is open, focus is inside it and the arrow keys move between options.

| Key | Action |
|---|---|
| `Enter` or `Space` or `ArrowDown` | On the field: opens the list and highlights the chosen option, or the first one. |
| `ArrowDown` or `ArrowUp` | Moves the highlight to the next or previous option. It stops at the ends. |
| `Home` or `End` | Moves the highlight to the first or the last option. |
| `A–Z` | Type-ahead: typing the start of a label moves the highlight to the next option that begins with it. |
| `Enter` or `Space` | Chooses the highlighted option, closes the list and returns focus to the field. |
| `Escape` | Closes the list without a change and returns focus to the field. |
| `Tab` | Closes the list when focus leaves it. |

## Behavior

What the select does with its value, focus, the list and motion.

- **Options:** The field is a combobox, the list a listbox and each option an option. The chosen option is marked selected and shows a check.
- **The label in the field:** The field shows the chosen option's label, not its value. Labels come from the item children, or from an items prop when options are built inside another component, and an icon shows in the field too.
- **Placeholder:** The placeholder shows in the tertiary text colour until an option is chosen.
- **Modal:** A select is modal by default: the page behind it is inert and does not scroll while the list is open. Turn modal off to keep the page usable, as the previews here do.
- **Position and size:** The list opens 4 px below the field, aligned to its start and as wide as it (8 rem at least). It flips above when there is no room and scrolls when it is taller than the space left.
- **Open field:** While the list is open the border of the field stays indigo, so the field and its list read as one.
- **Invalid and disabled:** An invalid trigger turns the border and focus ring red, so say why in a line under the field, linked with aria-describedby. A disabled select dims to 40% and ignores pointer and keyboard.
- **Previews in these docs:** The real list floats over the page. The previews here draw it inside the card, only while the whole card is on screen, and behave like the real one when you point at a card or focus it.
- **Opening and closing:** The list fades in and scales up from the field while sliding 4 px in from below. Closing is one step faster.
- **Reduced motion:** With reduced motion the transition is off and the list appears and disappears at once.
- **Inline width:** The inline trigger is only as wide as the current label, so text after it moves when a longer or shorter option is chosen. Keep labels of similar length.

## Anatomy

- `Select`: The root. It holds the chosen value and open state and renders nothing itself, and it reads option labels from its items, so the field shows Germany for the value de before the list ever opens.
- `SelectTrigger`: The field: a button that shows the chosen option and opens the list, 36 px high with an indigo border while open. Give it a visible label or an aria-label.
- `SelectValue`: The text in the field: the chosen option's label, or the placeholder in a quieter colour while nothing is chosen. An option with an icon shows the icon here too.
- `Chevron`: The 16 px chevron at the right of the field. It is part of the trigger and hidden from assistive technology.
- `SelectContent`: The list panel. It opens below the field at the same width and scrolls when it runs out of room, and its 12 px corner is concentric with the 8 px rows inside its 4 px padding.
- `SelectItem`: One option, 32 px high. Its value is what the select holds and its children are the label, with an optional icon in front; the highlighted option gets the page fill.
- `Check`: A 16 px check at the right of the chosen option.

## Best practices

### Do

- Give the select a visible label, joined to the trigger with an id. The placeholder is a hint such as Select a status, not the name of the field.
- Set a placeholder for a field that starts empty and say what is wanted: Select a status, not just Select.
- Keep the labels short and parallel. The list is as wide as the field, and a long label is cut off.
- Mark an invalid select as invalid on the trigger, and say why in a line under it, linked with aria-describedby.
- Inline: Give every trigger an aria-label that names what is chosen, such as Sort order or Status of WUI-482. There is no visible label, and the announced value alone doesn't say what the choice is for.

### Don't

- Don't use it for a long list. Past a dozen options people scroll instead of reading; a Combobox lets them type.
- Don't use it for two or three options that fit on the screen. Radio buttons show every option at once and need one click less.
- Don't use it for actions. Choosing an option sets a value; a menu of actions belongs in a Dropdown Menu.
- Inline: Don't use it as a form field. Without a border it doesn't look like an input; use Select with a label inside a Field.
- Inline: Don't let long labels push the sentence around. The trigger is as wide as the current label, so text after it moves when the value changes; keep labels about the same length.

## API reference

### Select

| Prop | Type | Default | Description |
|---|---|---|---|
| actionsRef | React.RefObject<SelectRootActions \| null> |  | A ref to imperative actions. - `unmount`: Manually unmounts the select. Call this after any externally controlled closing animation finishes. |
| autoComplete | string |  | Provides a hint to the browser for autofill. |
| children | React.ReactNode |  |  |
| defaultOpen | boolean | false | Whether the list starts open when you do not control it. |
| defaultValue | SelectValueType<Value, Multiple> \| null |  | The value that is chosen when you do not control the select. |
| disabled | boolean | false | Disables the select: it dims to 40 % and ignores the pointer and the keyboard. |
| form | string |  | Identifies the form that owns the hidden input. Useful when the select is rendered outside the form. |
| highlightItemOnHover | boolean | true | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| id | string |  | The id of the Select. |
| inputRef | React.Ref<HTMLInputElement> |  | A ref to access the hidden input element. |
| isItemEqualToValue | ((itemValue: Value, value: Value) => boolean) |  | Decides whether an option is the chosen value. Needed when values are objects that are not the same references. Defaults to `Object.is`. |
| items | Record<string, React.ReactNode> \| ReadonlyArray<{ label: React.ReactNode; value: any; }> \| ReadonlyArray<Group<any>> |  | The options as data, `{ value, label }` pairs or a map from value to label. Gives the field its labels when the `SelectItem` elements are built inside another component. Read from the children when you leave it out. |
| itemToStringLabel | ((itemValue: Value) => string) |  | Turns an object value into the text shown in the field. Needed when the values are objects. |
| itemToStringValue | ((itemValue: Value) => string) |  | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatical… |
| modal | boolean | true | When `true`, the default, the page behind the open list is inert and does not scroll. `false` leaves the page usable. |
| multiple | Multiple | false | Lets people choose several options. The value becomes an array. The field shows no chips, so show the choices yourself. |
| name | string |  | The name the value is submitted under in a form, through a hidden input. |
| onOpenChange | ((open: boolean, eventDetails: SelectRootChangeEventDetails) => void) |  | Called when the list opens or closes, with the reason, such as a click, a key press, an outside press or choosing an option. |
| onOpenChangeComplete | ((open: boolean) => void) |  | Event handler called after any animations complete when the select popup is opened or closed. |
| onValueChange | ((value: SelectValueType<Value, Multiple> \| (Multiple extends true ? never : null), eventDetails: SelectRootChangeEventDetails) => void) |  | Called with the value of the option the person chose. In a controlled select the field only changes when you set `value`. |
| open | boolean |  | Whether the list is open, when you control it. Pair it with `onOpenChange`. |
| readOnly | boolean | false | Keeps the value but stops people from choosing another option. |
| required | boolean | false | Marks the field as required for the form it belongs to. |
| value | SelectValueType<Value, Multiple> \| null |  | The chosen option's value, or `null` while nothing is chosen. It is a controlled value: update it in `onValueChange`. |

### InlineSelect

| Prop | Type | Default | Description |
|---|---|---|---|
| actionsRef | React.RefObject<SelectRootActions \| null> |  | A ref to imperative actions. - `unmount`: Manually unmounts the select. Call this after any externally controlled closing animation finishes. |
| autoComplete | string |  | Provides a hint to the browser for autofill. |
| children | React.ReactNode |  |  |
| defaultOpen | boolean | false | Whether the select popup is initially open. To render a controlled select popup, use the `open` prop instead. |
| defaultValue | SelectValueType<Value, Multiple> \| null |  | The uncontrolled value of the select when it's initially rendered. To render a controlled select, use the `value` prop instead. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| form | string |  | Identifies the form that owns the hidden input. Useful when the select is rendered outside the form. |
| highlightItemOnHover | boolean | true | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| id | string |  | The id of the Select. |
| inputRef | React.Ref<HTMLInputElement> |  | A ref to access the hidden input element. |
| isItemEqualToValue | ((itemValue: Value, value: Value) => boolean) |  | Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to `Object.is` comparison. |
| items | Record<string, React.ReactNode> \| ReadonlyArray<{ label: React.ReactNode; value: any; }> \| ReadonlyArray<Group<any>> |  | Data structure of the items rendered in the select popup. When specified, `<Select.Value>` renders the label of the selected item instead of the raw value. |
| itemToStringLabel | ((itemValue: Value) => string) |  | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger. If the shape of the object is `{ value, label }`, the label will be used auto… |
| itemToStringValue | ((itemValue: Value) => string) |  | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatical… |
| modal | boolean | true | Determines if the select enters a modal state when open. - `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the re… |
| multiple | Multiple | false | Whether multiple items can be selected. |
| name | string |  | Identifies the field when a form is submitted. |
| onOpenChange | ((open: boolean, eventDetails: SelectRootChangeEventDetails) => void) |  | Event handler called when the select popup is opened or closed. |
| onOpenChangeComplete | ((open: boolean) => void) |  | Event handler called after any animations complete when the select popup is opened or closed. |
| onValueChange | ((value: SelectValueType<Value, Multiple> \| (Multiple extends true ? never : null), eventDetails: SelectRootChangeEventDetails) => void) |  | Event handler called when the value of the select changes. |
| open | boolean |  | Whether the select popup is currently open. |
| readOnly | boolean | false | Whether the user should be unable to choose a different option from the select popup. |
| required | boolean | false | Whether the user must choose a value before submitting a form. |
| value | SelectValueType<Value, Multiple> \| null |  | The value of the select. Use when controlled. |

### InlineSelectContent

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| finalFocus | boolean \| React.RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void) |  | Determines the element to focus when the select popup is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `fun… |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

### InlineSelectItem

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| label | string |  | Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided. |
| nativeButton | boolean | false | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| value | any | null | A unique value that identifies this select item. |

Also accepts React.HTMLAttributes attributes.

### InlineSelectTrigger

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| disabled | boolean |  | Whether the component should ignore user interaction. |
| nativeButton | boolean | true | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.ButtonHTMLAttributes attributes.

### InlineSelectValue

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode \| ((value: any) => React.ReactNode) |  | Accepts a function that returns a `ReactNode` to format the selected value. Treat the value as read-only: in `multiple` mode it may be a shared frozen array when nothing is selected. |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| placeholder | React.ReactNode |  | The placeholder value to display when no value is selected. This is overridden by `children` if specified, or by a null item's label in `items`. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

### NativeSelect

| Prop | Type | Default | Description |
|---|---|---|---|
| actionsRef | React.RefObject<SelectRootActions \| null> |  | A ref to imperative actions. - `unmount`: Manually unmounts the select. Call this after any externally controlled closing animation finishes. |
| autoComplete | string |  | Provides a hint to the browser for autofill. |
| children | React.ReactNode |  |  |
| defaultOpen | boolean | false | Whether the select popup is initially open. To render a controlled select popup, use the `open` prop instead. |
| defaultValue | SelectValueType<Value, Multiple> \| null |  | The uncontrolled value of the select when it's initially rendered. To render a controlled select, use the `value` prop instead. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| form | string |  | Identifies the form that owns the hidden input. Useful when the select is rendered outside the form. |
| highlightItemOnHover | boolean | true | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
| id | string |  | The id of the Select. |
| inputRef | React.Ref<HTMLInputElement> |  | A ref to access the hidden input element. |
| isItemEqualToValue | ((itemValue: Value, value: Value) => boolean) |  | Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to `Object.is` comparison. |
| items | Record<string, React.ReactNode> \| ReadonlyArray<{ label: React.ReactNode; value: any; }> \| ReadonlyArray<Group<any>> |  | Data structure of the items rendered in the select popup. When specified, `<Select.Value>` renders the label of the selected item instead of the raw value. |
| itemToStringLabel | ((itemValue: Value) => string) |  | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger. If the shape of the object is `{ value, label }`, the label will be used auto… |
| itemToStringValue | ((itemValue: Value) => string) |  | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatical… |
| modal | boolean | true | Determines if the select enters a modal state when open. - `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the re… |
| multiple | Multiple | false | Whether multiple items can be selected. |
| name | string |  | Identifies the field when a form is submitted. |
| onOpenChange | ((open: boolean, eventDetails: SelectRootChangeEventDetails) => void) |  | Event handler called when the select popup is opened or closed. |
| onOpenChangeComplete | ((open: boolean) => void) |  | Event handler called after any animations complete when the select popup is opened or closed. |
| onValueChange | ((value: SelectValueType<Value, Multiple> \| (Multiple extends true ? never : null), eventDetails: SelectRootChangeEventDetails) => void) |  | Event handler called when the value of the select changes. |
| open | boolean |  | Whether the select popup is currently open. |
| readOnly | boolean | false | Whether the user should be unable to choose a different option from the select popup. |
| required | boolean | false | Whether the user must choose a value before submitting a form. |
| value | SelectValueType<Value, Multiple> \| null |  | The value of the select. Use when controlled. |

### NativeSelectContent

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| finalFocus | boolean \| React.RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void) |  | Determines the element to focus when the select popup is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `fun… |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

### NativeSelectItem

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| disabled | boolean | false | Whether the component should ignore user interaction. |
| label | string |  | Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided. |
| nativeButton | boolean | false | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| value | any | null | A unique value that identifies this select item. |

Also accepts React.HTMLAttributes attributes.

### NativeSelectTrigger

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| disabled | boolean |  | Whether the component should ignore user interaction. |
| nativeButton | boolean | true | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| variant | "field" \| "inline" |  |  |

Also accepts React.ButtonHTMLAttributes attributes.

### NativeSelectValue

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode \| ((value: any) => React.ReactNode) |  | Accepts a function that returns a `ReactNode` to format the selected value. Treat the value as read-only: in `multiple` mode it may be a shared frozen array when nothing is selected. |
| className | string \| ((state: State) => string \| undefined) |  | CSS class applied to the element, or a function that returns a class based on the component's state. |
| placeholder | React.ReactNode |  | The placeholder value to display when no value is selected. This is overridden by `children` if specified, or by a null item's label in `items`. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

### SelectContent

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | Extra classes for the list panel, for example `min-w-64` to make it wider than the field. |
| finalFocus | boolean \| React.RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void) |  | Where focus goes when the list closes. It returns to the field by default. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

### SelectItem

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | Extra classes for the row. |
| disabled | boolean | false | Stops the option from being highlighted or chosen. |
| label | string |  | The text used for type-ahead and for the field when the children are not plain text. |
| nativeButton | boolean | false | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| value | any | null | The value the select holds when this option is chosen. |

Also accepts React.HTMLAttributes attributes.

### SelectTrigger

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode |  |  |
| className | string \| ((state: State) => string \| undefined) |  | Extra classes for the field, for example `h-(--field-lg)` for the large height. |
| disabled | boolean |  | Disables this trigger only. Prefer `disabled` on the root. |
| nativeButton | boolean | true | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| variant | "field" \| "inline" | "field" | `field` (default) is the full-width form field with a border. `inline` is a compact trigger with no border that is only as wide as its label and sits inside a sentence, a toolbar, a title or a row; it replaces InlineSelect. Give an inline trigger an `aria-label`, because it has no visible label. |

Also accepts React.ButtonHTMLAttributes attributes.

### SelectValue

| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode \| ((value: any) => React.ReactNode) |  | Replaces the text in the field with your own content, or a function of the chosen value. |
| className | string \| ((state: State) => string \| undefined) |  | Extra classes for the text in the field. It is a flex row, so an icon and a label sit side by side. |
| placeholder | React.ReactNode |  | The text shown in the field while nothing is chosen, in the quieter tertiary colour. |
| render | React.ReactElement \| ComponentRenderFn<RenderFunctionProps, State> |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| style | React.CSSProperties \| ((state: State) => React.CSSProperties \| undefined) |  | Style applied to the element, or a function that returns a style object based on the component's state. |

Also accepts React.HTMLAttributes attributes.

## Examples

### Content: text or an icon with the text

```tsx
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="status">Status</Label>
      <Select>
        <SelectTrigger id="status">
          <SelectValue placeholder="Select a status" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="backlog">Backlog</SelectItem>
          <SelectItem value="todo">To do</SelectItem>
          <SelectItem value="in-progress">In progress</SelectItem>
          <SelectItem value="in-review">In review</SelectItem>
          <SelectItem value="done">Done</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Sizes: 36 and 40 px

```tsx
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="status">Status</Label>
      <Select>
        <SelectTrigger id="status">
          <SelectValue placeholder="Select a status" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="backlog">Backlog</SelectItem>
          <SelectItem value="todo">To do</SelectItem>
          <SelectItem value="in-progress">In progress</SelectItem>
          <SelectItem value="in-review">In review</SelectItem>
          <SelectItem value="done">Done</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

### States: default, filled, invalid and disabled

```tsx
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="status">Status</Label>
      <Select>
        <SelectTrigger id="status">
          <SelectValue placeholder="Select a status" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="backlog">Backlog</SelectItem>
          <SelectItem value="todo">To do</SelectItem>
          <SelectItem value="in-progress">In progress</SelectItem>
          <SelectItem value="in-review">In review</SelectItem>
          <SelectItem value="done">Done</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Inline: in a sentence, in a toolbar or in a heading

```tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <p className="flex items-center gap-1 text-sm text-text-secondary">
      Sort projects by
      <Select defaultValue="newest">
        <SelectTrigger variant="inline" aria-label="Sort order">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="newest">Newest</SelectItem>
          <SelectItem value="oldest">Oldest</SelectItem>
          <SelectItem value="name">Name (A–Z)</SelectItem>
          <SelectItem value="activity">Last activity</SelectItem>
        </SelectContent>
      </Select>
    </p>
  )
}
```

### Inline content: text, icons or a long list

```tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <p className="flex items-center gap-1 text-sm text-text-secondary">
      Sort projects by
      <Select defaultValue="newest">
        <SelectTrigger variant="inline" aria-label="Sort order">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="newest">Newest</SelectItem>
          <SelectItem value="oldest">Oldest</SelectItem>
          <SelectItem value="name">Name (A–Z)</SelectItem>
          <SelectItem value="activity">Last activity</SelectItem>
        </SelectContent>
      </Select>
    </p>
  )
}
```

### Inline states: rest, hover, open, focus, placeholder and disabled

```tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function Example() {
  return (
    <Select defaultValue="newest">
      <SelectTrigger variant="inline" aria-label="Sort order">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="newest">Newest</SelectItem>
        <SelectItem value="oldest">Oldest</SelectItem>
        <SelectItem value="name">Name (A–Z)</SelectItem>
        <SelectItem value="activity">Last activity</SelectItem>
      </SelectContent>
    </Select>
  )
}
```

## In context

### Sort order above a list

A narrow select in a toolbar: the choice applies at once and the list follows. Press Enter or Space on the field to open it.

```tsx
import { useState } from "react"
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const projects = [
  { name: "Q4 roadmap", status: "In progress", created: 4 },
  { name: "Pricing experiment", status: "In review", created: 3 },
  { name: "Onboarding revamp", status: "To do", created: 2 },
  { name: "Brand refresh", status: "Done", created: 1 },
]

export function SortProjects() {
  const [sort, setSort] = useState("newest")
  const rows = [...projects].sort((a, b) =>
    sort === "name" ? a.name.localeCompare(b.name) : sort === "oldest" ? a.created - b.created : b.created - a.created
  )

  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-center justify-between gap-4">
        <h3 className="text-sm font-semibold text-foreground">Projects</h3>
        <div className="flex w-44 flex-col gap-2">
          <Label htmlFor="sort" className="sr-only">
            Sort by
          </Label>
          <Select value={sort} onValueChange={setSort}>
            <SelectTrigger id="sort">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="newest">Newest first</SelectItem>
              <SelectItem value="oldest">Oldest first</SelectItem>
              <SelectItem value="name">Name A to Z</SelectItem>
            </SelectContent>
          </Select>
        </div>
      </div>
      <ul className="divide-y divide-border overflow-hidden rounded-xl border border-border bg-card">
        {rows.map((row) => (
          <li key={row.name} className="flex h-11 items-center justify-between px-4 text-sm">
            <span className="font-medium text-foreground">{row.name}</span>
            <span className="text-text-secondary">{row.status}</span>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

### Plan and billing period

Two selects feed one line of text. Choosing a plan or billing period updates the price under them.

```tsx
import { useState } from "react"
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const prices: Record<string, number> = { starter: 12, team: 24, business: 48 }

export function PlanPicker() {
  const [plan, setPlan] = useState("team")
  const [billing, setBilling] = useState("yearly")
  const price = billing === "yearly" ? prices[plan] : Math.round(prices[plan] * 1.25)

  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-col gap-2">
        <Label htmlFor="plan">Plan</Label>
        <Select value={plan} onValueChange={setPlan}>
          <SelectTrigger id="plan">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="starter">Starter</SelectItem>
            <SelectItem value="team">Team</SelectItem>
            <SelectItem value="business">Business</SelectItem>
          </SelectContent>
        </Select>
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="billing">Billing</Label>
        <Select value={billing} onValueChange={setBilling}>
          <SelectTrigger id="billing">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="yearly">Yearly, save 20%</SelectItem>
            <SelectItem value="monthly">Monthly</SelectItem>
          </SelectContent>
        </Select>
      </div>
      <p role="status" className="text-sm text-text-secondary">
        <span className="font-medium text-foreground">${price}</span> per seat per month, billed {billing}.
      </p>
    </div>
  )
}
```

### Role in an invitation

The line under the select explains what the chosen role allows, so nobody has to open the list to learn what Editor means.

```tsx
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const roles: Record<string, string> = {
  admin: "Admins manage members, billing and every project.",
  editor: "Editors change content in the projects they join.",
  viewer: "Viewers read and comment, and cannot change anything.",
}

export function InviteTeammate() {
  const [role, setRole] = useState("editor")
  const [sent, setSent] = useState(false)

  return (
    <form
      className="flex flex-col gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        setSent(true)
      }}
    >
      <div className="flex flex-col gap-2">
        <Label htmlFor="email">Email</Label>
        <Input id="email" type="email" defaultValue="info@wunderui.com" />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="role">Role</Label>
        <Select value={role} onValueChange={setRole}>
          <SelectTrigger id="role">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="admin">Admin</SelectItem>
            <SelectItem value="editor">Editor</SelectItem>
            <SelectItem value="viewer">Viewer</SelectItem>
          </SelectContent>
        </Select>
        <p className="text-xs text-text-tertiary">{roles[role]}</p>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" variant="gradient">
          Send invite
        </Button>
        <span role="status" className="text-sm text-text-secondary">
          {sent ? "Invite sent." : ""}
        </span>
      </div>
    </form>
  )
}
```

### Activity filter in a sentence

Two inline selects in one sentence filter the list below. The list answers at once, and each select names what it chooses.

```tsx
import { useState } from "react"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const EVENTS = [
  { who: "Nora Lindgren", what: "commented on Q4 roadmap", kind: "comments", days: 1 },
  { who: "Daniel Okafor", what: "changed the billing plan", kind: "billing", days: 3 },
  { who: "Priya Raman", what: "commented on Pricing page", kind: "comments", days: 6 },
  { who: "Tomás Ferreira", what: "invited Maya Kessler", kind: "members", days: 12 },
  { who: "Maya Kessler", what: "exported the invoices", kind: "billing", days: 25 },
  { who: "Nora Lindgren", what: "renamed the workspace", kind: "members", days: 40 },
]

export function Example() {
  const [kind, setKind] = useState("all")
  const [days, setDays] = useState("30")
  const events = EVENTS.filter((event) => (kind === "all" || event.kind === kind) && event.days <= Number(days))

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <p className="flex flex-wrap items-center gap-x-1 text-sm text-text-secondary">
        Show
        <Select value={kind} onValueChange={(next) => setKind(next ?? "all")}>
          <SelectTrigger variant="inline" aria-label="Activity type">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="all">all activity</SelectItem>
            <SelectItem value="comments">comments</SelectItem>
            <SelectItem value="billing">billing</SelectItem>
            <SelectItem value="members">members</SelectItem>
          </SelectContent>
        </Select>
        from the last
        <Select value={days} onValueChange={(next) => setDays(next ?? "30")}>
          <SelectTrigger variant="inline" aria-label="Period">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="7">7 days</SelectItem>
            <SelectItem value="30">30 days</SelectItem>
            <SelectItem value="90">90 days</SelectItem>
          </SelectContent>
        </Select>
      </p>
      <ul className="flex flex-col gap-2 text-sm" aria-live="polite">
        {events.map((event) => (
          <li key={event.what} className="flex items-baseline justify-between gap-4">
            <span className="text-foreground">
              <span className="font-medium">{event.who}</span> {event.what}
            </span>
            <span className="shrink-0 text-text-tertiary">{event.days} d ago</span>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

### Period in a card title

The period sits in the title of a revenue card, so the card needs no extra control. The figures change with it and are announced politely.

```tsx
import { useState } from "react"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const REVENUE: Record<string, { total: string; change: string }> = {
  "7d": { total: "$12,480", change: "+4.2%" },
  "30d": { total: "$48,920", change: "+8.9%" },
  "90d": { total: "$141,300", change: "+12.4%" },
  "12m": { total: "$562,840", change: "+21.7%" },
}

export function Example() {
  const [period, setPeriod] = useState("30d")
  const figures = REVENUE[period]

  return (
    <div className="flex w-full max-w-sm flex-col gap-2 rounded-xl border border-border bg-card p-4">
      <h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
        Revenue, last
        <Select value={period} onValueChange={(next) => setPeriod(next ?? "30d")}>
          <SelectTrigger variant="inline" aria-label="Period">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="7d">7 days</SelectItem>
            <SelectItem value="30d">30 days</SelectItem>
            <SelectItem value="90d">90 days</SelectItem>
            <SelectItem value="12m">12 months</SelectItem>
          </SelectContent>
        </Select>
      </h3>
      <p aria-live="polite" className="text-3xl font-semibold tabular-nums text-foreground">
        {figures.total}
      </p>
      <p className="text-sm text-text-secondary">{figures.change} compared with the period before</p>
    </div>
  )
}
```

### Status at the end of a row

Each task has its own status select with icons. The trigger is named with the task, so a screen reader hears which row it changes.

```tsx
import { useState } from "react"
import { CircleCheck, Eye, Inbox, ListTodo } from "lucide-react"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const TASKS = [
  { id: "WUI-482", title: "Menu closes on the first click", status: "review" },
  { id: "WUI-475", title: "Dark mode contrast on tags", status: "todo" },
  { id: "WUI-471", title: "Pricing table on mobile", status: "backlog" },
]

export function Example() {
  const [tasks, setTasks] = useState(TASKS)
  const setStatus = (id: string, status: string) => setTasks((current) => current.map((task) => (task.id === id ? { ...task, status } : task)))

  return (
    <ul className="flex w-full max-w-md flex-col divide-y divide-border rounded-xl border border-border bg-card">
      {tasks.map((task) => (
        <li key={task.id} className="flex items-center gap-3 p-2 pl-4">
          <span className="w-16 shrink-0 text-xs text-text-tertiary">{task.id}</span>
          <span className="min-w-0 flex-1 truncate text-sm text-foreground">{task.title}</span>
          <Select value={task.status} onValueChange={(next) => setStatus(task.id, next ?? task.status)}>
            <SelectTrigger variant="inline" aria-label={`Status of ${task.id}`}>
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="backlog"><Inbox className="size-4" aria-hidden />Backlog</SelectItem>
              <SelectItem value="todo"><ListTodo className="size-4" aria-hidden />To do</SelectItem>
              <SelectItem value="review"><Eye className="size-4" aria-hidden />In review</SelectItem>
              <SelectItem value="done"><CircleCheck className="size-4" aria-hidden />Done</SelectItem>
            </SelectContent>
          </Select>
        </li>
      ))}
    </ul>
  )
}
```

## For AI agents

`get_component("select")`

Hand your agent this page as Markdown or the WunderUI MCP server, not a screenshot.
