Select
Choose one or more options from a popup list.
Select exposes a select-only combobox with a listbox popup. It supports single and multiple selection, grouped options, custom rendering, and native form integration.
Import
import { Select } from "@puzzle-ui/ui";Basic usage
Provide a visible label and an array of options with value and plain-text label. placeholder is shown until a value is selected. Disabled options remain visible but cannot be chosen.
Multiple selection and clearing
multiple changes the value to an array and keeps the popup open after selection by default. maxSelected limits the number of selected options. clearable adds a clear action; customize its accessible name with clearLabel.
Clearing returns undefined in single mode and [] in multiple mode. Set closeOnSelect explicitly to override either mode's default popup behavior.
Sizes and width
size supports sm, md, and lg. fullWidth fills the available width. Both accept responsive objects with mobile, tablet, and desktop keys.
Groups, descriptions, and custom rendering
group adds headings within the list. An option's description adds supporting text. Use renderOption to customize its contents without adding nested interactive controls. Its state includes selected, active, and multiple.
renderValue receives one selected option or undefined in single mode, and an array in multiple mode.
Custom option rendering replaces the default description display. Include option.description in your renderer, as above, to keep it visible.
Controlled value
Pair value with onChange. In single mode the callback receives T | undefined; in multiple mode it receives T[]. Passing value={undefined} represents a controlled empty selection. Use defaultValue only for uncontrolled selection.
Popup placement and empty options
Control popup visibility with open and onOpenChange, or set its initial visibility with defaultOpen. Placement may fall back when space is limited. menuMaxHeight constrains the scrollable list; matchTriggerWidth={false} allows a narrower menu.
Validation and disabled state
required participates in native validation when name enables the hidden native select. error displays a message and marks the control invalid; error={true} uses "Invalid value". disabled prevents interaction.
Select combines aria-invalid with its own error, and data-invalid matches the result. An external aria-describedby is preserved before its own description and error IDs, so Select composes with FormField.
Object values and native forms
For object values, use isOptionEqualToValue to compare stable identifiers instead of object identity. Supply serializeValue with a unique string for native form submission. name enables a visually hidden native select; form associates it with a form elsewhere in the document.
API
Selection and field props
SelectProps<T> distinguishes single and multiple modes. ResponsiveValue<T> accepts a value or a mobile/tablet/desktop object.
| Prop | Type | Default | Description |
|---|---|---|---|
options | readonly SelectOption<T>[] | Required | Available options. |
multiple | boolean | false | Enables array-valued selection. |
value | T | undefined or readonly T[] | - | Controlled selection for the chosen mode. |
defaultValue | T | undefined or readonly T[] | undefined or [] | Initial uncontrolled selection. |
onChange | (value: T | undefined) => void or (value: T[]) => void | - | Next selection, not a DOM event. |
label | ReactNode | - | Visible field label. |
placeholder | ReactNode | "Select an option" | Content when nothing is selected. |
description | ReactNode | - | Supporting field text. |
error | ReactNode | boolean | - | Error message; true uses "Invalid value". |
disabled | boolean | false | Disables the control. |
required | boolean | false | Marks the field required. |
clearable | boolean | false | Adds a clear-selection action. |
clearLabel | string | "Clear selection" | Accessible name of the clear action. |
maxSelected | number | - | Maximum simultaneous selections in multiple mode. |
size | ResponsiveValue<"sm" | "md" | "lg"> | "md" | Control size. |
fullWidth | ResponsiveValue<boolean> | false | Fills available width. |
Popup props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled popup visibility. |
defaultOpen | boolean | false | Initial uncontrolled popup visibility. |
onOpenChange | (open: boolean) => void | - | Requested visibility changes. |
position | "top" | "right" | "bottom" | "left" | "bottom" | Preferred popup side. |
align | "start" | "center" | "end" | "start" | Popup alignment. |
offset | number | 8 | Gap from the trigger in pixels. |
collisionPadding | number | 8 | Minimum viewport-edge distance in pixels. |
closeOnSelect | boolean | !multiple | Closes the popup after selection. |
matchTriggerWidth | boolean | true | Matches popup minimum width to the trigger. |
menuMaxHeight | CSSProperties["maxHeight"] | "18rem" | Maximum list height. |
noOptionsText | ReactNode | "No options" | Empty-list content. |
Rendering, forms, and styling
| Prop | Type | Default | Description |
|---|---|---|---|
renderOption | (option: SelectOption<T>, state: SelectOptionRenderState) => ReactNode | - | Custom option contents. |
renderValue | (selected: SelectOption<T> | readonly SelectOption<T>[] | undefined) => ReactNode | - | Custom selected-value contents. |
isOptionEqualToValue | (option: SelectOption<T>, value: T) => boolean | Identity comparison | Compares options with selected values. |
serializeValue | (value: T) => string | String conversion | Serializes values for native form submission. |
name | string | - | Enables and names the hidden native select. |
form | string | - | Associated form ID. |
autoComplete | string | - | Native select autocomplete attribute. |
className | string | - | Additional class on the combobox trigger. |
rootClassName | string | - | Additional class on the outer field. |
popoverClassName | string | - | Additional class on popup content. |
menuClassName | string | - | Additional class on the listbox. |
Other supported button attributes, including id, aria-label, aria-labelledby, and aria-describedby, apply to the trigger.
SelectOption and render state
| Option prop | Type | Default | Description |
|---|---|---|---|
id | string | number | - | Stable identifier for reordered options. |
value | T | Required | Value returned on selection. |
label | string | Required | Plain-text label for display, typeahead, and accessibility. |
description | ReactNode | - | Supporting option text. |
group | string | - | Group heading. |
disabled | boolean | false | Prevents selection. |
SelectOptionRenderState contains three booleans: selected (chosen value), active (keyboard-highlighted option), and multiple (selection mode).
Accessibility
Prefer a visible label; when omitted, provide aria-label or aria-labelledby. The combobox keeps DOM focus while its popup is open and exposes the highlighted option with aria-activedescendant. It supports arrow-key navigation, Home/End, typeahead, and Enter/Space to open or select. Escape closes the list. Keep custom option content non-interactive.