API and Development
Select displays a dropdown menu of predefined options, allowing users to choose one value from a list. It supports both flat and grouped options, with optional placeholder text and disabled states. It provides a standard dropdown selection interface where users choose from predefined options. Built on Radix UI's Select component, it ensures consistent selection patterns across the interface with full keyboard and screen reader accessibility. Ideal for forms, filters, and configuration where users select from known options without needing to search.
| Name | Type | Default | Description |
|---|---|---|---|
label | LocalizedMessage | — | Visible label. It names the control and the list. |
options | readonly SelectEntry[] | — | Entries of the list: options, labelled groups of options, and separators. Option values must be unique. |
align | Align | "start" | Alignment along that edge. |
autoFocus | boolean | false | Focuses the trigger when the component mounts. It does not open the list. |
avoidCollisions | boolean | true | Flips to the opposite edge and shifts along the edge to stay inside the collision boundary. Pass false to keep
the requested side and align even where the content overflows. Turning it off, or setting collisionPadding
or collisionBoundary, places the content with the measured fallback instead of CSS anchoring. |
className | string | — | Classes on the root. Put overrides in @layer chroma.overrides. |
clearable | boolean | — | No clear button. Shows a clear button while a value is selected. |
clearLabel | LocalizedMessage | — | Accessible name of the clear button. |
collisionBoundary | Element | readonly (Element | null)[] | null | — | Elements the content must stay inside, in addition to the viewport, such as an open drawer to flip around. The
content fits the intersection of the viewport and every connected boundary. null entries are skipped, and a prop that is still null already selects the measured fallback, so a boundary that mounts later does not move the content. Ignored
when avoidCollisions is false. |
collisionPadding | number | Partial<Record<"blockStart" | "blockEnd" | "inlineStart" | "inlineEnd", number>> | — | Distance in CSS pixels the content keeps from the collision boundary edges: one number for all four, or logical
edges (blockStart, blockEnd, inlineStart, inlineEnd). Omitted edges keep the default viewport inset.
Ignored when avoidCollisions is false. |
container | Element | null | — | Mount node in the trigger's document. Defaults to that document's body. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
defaultValue | string | readonly string[] | null | — | Initial value when uncontrolled. Initial values when uncontrolled. |
description | LocalizedMessage | — | Supporting copy under the control. The trigger is described by it. |
disableClientFiltering | boolean | false | Turns off the built-in filtering, for a list the owner narrows itself from onSearchChange. |
disabled | boolean | false | Takes the control out of the tab order and out of the form. |
emptyLabel | LocalizedMessage | — | Message shown, and announced, when the query leaves no option. |
error | LocalizedMessage | — | Error copy under the control, shown while invalid is true. |
estimatedOptionSize | number | 34 | Block size in pixels the browser reserves for a row it has not laid out, while virtualized. A closer estimate
keeps the scrollbar steady. |
form | string | — | Id of the form the control belongs to, when it is not inside one. |
formatValue | ((selected: readonly SelectOption[]) => LocalizedMessage | DisplayValue) | — | Only for multiple.
Text the trigger shows for the selected options. Defaults to their labels, comma separated. |
hideLabel | boolean | false | Keeps the label for assistive technology without painting it. |
id | string | — | Id of the trigger, for a label or description that lives outside the component. Generated when omitted. |
indicator | SelectIndicator | SelectIndicator.Check | Mark an option shows at its inline end when selected. |
invalid | boolean | false | Paints the destructive edge and exposes aria-invalid. Pair it with error. |
loop | boolean | false | Whether ArrowDown on the last option wraps to the first and ArrowUp on the first wraps to the last. |
multiple | boolean | — | One option is selected at a time and choosing it closes the list. Any number of options are selected and the list stays open while they change. |
name | string | — | Form field name. Each selected value is submitted under it; nothing is submitted while empty. |
onBlur | FocusEventHandler<HTMLElement> | — | Called once when focus leaves the whole control, popup included, for somewhere outside it, such as the next field or a press outside the list. It is not called when the list opens, when an option is chosen, or when focus moves between the trigger and the search input, so a form library validates on blur once per visit. |
onFocus | FocusEventHandler<HTMLElement> | — | Called once when focus enters the control from outside it: the trigger, the clear button, or the search input of
the list. It is not called when focus moves between those parts or into the list, and not when the list opens or
closes. Use it with onBlur to track whether the field was touched. |
onOpenChange | SelectOpenChangeHandler | — | Called on trigger press ("trigger-press"), option press or Enter ("item-press"), Tab ("focus-out"), and
Escape or an outside press ("light-dismiss", not cancelable). |
onSearchChange | ((query: string, details: ChangeDetails<SelectSearchChangeReason>) => void) | — | Called with the text of the search input each time it changes, and with "" when the list closes with text in it. |
onValueChange | ((value: string | null, details: ChangeDetails<CollectionValueChangeReason>) => void) | ((value: string[], details: ChangeDetails<CollectionValueChangeReason>) => void) | — | Called when the value changes, with null after the clear button. Cancel the change through
details.cancel(). The reason is "item-press", "list-navigation" for an arrow key or typeahead on the closed
trigger, or "form-reset", which is not cancelable.
Called when the values change, in option order. Cancel the change through details.cancel(). |
open | boolean | — | Controlled open state. Pair it with onOpenChange. |
placeholder | LocalizedMessage | — | Text shown while nothing is selected. It is not an option and submits nothing. |
readOnly | boolean | false | Keeps the control focusable and submitted but stops it from opening or changing. |
ref | Ref<HTMLButtonElement> | — | Ref to the trigger button. |
required | boolean | false | Marks the label and sets aria-required. |
requiredHint | LocalizedMessage | — | Form-level explanation of required fields, shown between the label and the control. |
searchable | boolean | — | No search input. Adds a search input that filters the options by their label. Focus moves into it on open. |
searchLabel | LocalizedMessage | — | Accessible name of the search input. |
searchPlaceholder | LocalizedMessage | — | Placeholder of the search input. |
side | Side | "block-end" | Preferred edge of the control. Flips to the opposite edge when there is no room. |
size | InputSize | InputSize.Large | Control scale from the shared control heights. |
triggerWidth | SelectTriggerWidth | SelectTriggerWidth.Fill | How the trigger sizes along the inline axis. fill keeps the full field width and truncates long values; content
sizes to the label and animates width and value crossfade on change; longest reserves the width of the widest
option, so the trigger keeps one width whatever is selected. |
value | string | readonly string[] | null | — | Controlled value. null is a controlled empty value.
Controlled values. |
virtualized | boolean | — | Skips layout and paint for rows outside the scrollport with content-visibility: auto; every row stays in the
DOM. Defaults to on when the list holds more than 100 options. |
Select when:
Combobox)Combobox when:
InputWithSelect when:
Input when: