Skip to content
Entangle UI v0.13.0

Select

Dropdown select component for choosing a single value from a list. Supports searchable filtering, grouped options, keyboard navigation, clearable selection, and multiple visual variants. Built for professional editor interfaces.

Live Preview

import { Select } from 'entangle-ui';
<Select
options={[
{ value: 'normal', label: 'Normal' },
{ value: 'multiply', label: 'Multiply' },
{ value: 'screen', label: 'Screen' },
]}
value={blendMode}
onChange={setBlendMode}
/>

The Select component supports both controlled and uncontrolled patterns.

Controlled

// Controlled
const [value, setValue] = useState<string | null>('normal');
<Select options={options} value={value} onChange={setValue} />
// Uncontrolled
<Select options={options} defaultValue="normal" />

Options can be organized into groups with non-selectable headers.

Grouped Options

<Select
label="Blend Mode"
options={[
{
label: 'Normal',
options: [
{ value: 'normal', label: 'Normal' },
{ value: 'dissolve', label: 'Dissolve' },
],
},
{
label: 'Darken',
options: [
{ value: 'multiply', label: 'Multiply' },
{ value: 'darken', label: 'Darken' },
{ value: 'color-burn', label: 'Color Burn' },
],
},
]}
value={mode}
onChange={setMode}
/>

Enable the searchable prop to show a filter input inside the dropdown. A custom filter function can be provided with filterFn.

Searchable

<Select
searchable
searchPlaceholder="Filter blend modes..."
emptyMessage="No matching modes"
options={allBlendModes}
value={mode}
onChange={setMode}
/>
// Custom filter logic
<Select
searchable
filterFn={(option, query) => option.value.startsWith(query.toLowerCase())}
options={options}
value={value}
onChange={setValue}
/>

When clearable is enabled, a clear button appears when a value is selected. The onChange value type follows clearable: without it the callback only ever receives a real value (T), so no null check is needed; with clearable it can also receive null when the selection is cleared.

Clearable

<Select
clearable
placeholder="Choose a material..."
options={materials}
value={material}
onChange={setMaterial}
/>

Each option can include an icon rendered before its label.

Options with Icons

<Select
options={[
{ value: 'mesh', label: 'Mesh', icon: <MeshIcon /> },
{ value: 'light', label: 'Light', icon: <LightIcon /> },
{ value: 'camera', label: 'Camera', icon: <CameraIcon /> },
]}
value={objectType}
onChange={setObjectType}
/>

By default the select shrinks to the width of its content, which is convenient inside toolbars but means it collapses when placed in a flex row. Set fullWidth to stretch the trigger to fill its container — ideal for property panels and forms.

Full Width

<Select
fullWidth
label="Framework"
options={frameworks}
value={v}
onChange={setV}
/>

The open dropdown sizes itself to its widest option (clamped to the viewport), so long option labels are never clipped even when the trigger is narrow. Use minDropdownWidth to set a floor and fullWidth to also widen the trigger.

Content-Sized Dropdown

// The trigger is 160px, but the dropdown grows to fit the longest label.
<Select
label="Shading Model"
options={[
{ value: 'std', label: 'Standard' },
{
value: 'pbr',
label: 'Physically Based Rendering (Metallic / Roughness)',
},
]}
value={model}
onChange={setModel}
/>

The variant prop controls the trigger button style.

Variants

<Select variant="default" options={opts} value={v} onChange={setV} />
<Select variant="ghost" options={opts} value={v} onChange={setV} />
<Select variant="filled" options={opts} value={v} onChange={setV} />

Sizes

<Select size="sm" options={opts} value={v} onChange={setV} />
<Select size="md" options={opts} value={v} onChange={setV} />
<Select size="lg" options={opts} value={v} onChange={setV} />

| Size | Trigger Height | Use case | | ---- | -------------- | ---------------- | | sm | 20px | Compact toolbars | | md | 24px | Standard panels | | lg | 32px | Prominent forms |

Labels and Helper Text

<Select
label="Render Engine"
helperText="Select the rendering backend"
required
options={engines}
value={engine}
onChange={setEngine}
/>

Error State

<Select
label="Material"
error
errorMessage="A material is required"
options={materials}
value={material}
onChange={setMaterial}
/>

Use the name prop for native form submission. A hidden <input> is rendered with the selected value.

<Select name="blendMode" options={blendModes} value={mode} onChange={setMode} />
Prop Type Default Description
value T | null Selected value (controlled).
defaultValue T Default selected value (uncontrolled).
options Array<SelectOptionItem<T> | SelectOptionGroup<T>> Options to display — flat list or grouped.
placeholder string 'Select...' Placeholder text when no value is selected.
searchable boolean false Whether to show a search input inside the dropdown.
searchPlaceholder string 'Search...' Placeholder for the search input.
filterFn (option: SelectOptionItem<T>, query: string) => boolean Custom filter function for searchable mode.
emptyMessage string 'No results found' Message shown when search yields no results.
size 'sm' | 'md' | 'lg' 'md' Size of the trigger button.
variant 'default' | 'ghost' | 'filled' 'default' Visual variant of the trigger button.
label string Label displayed above the select.
helperText string Helper text displayed below the select.
error boolean false Error state.
errorMessage string Error message shown when in error state.
disabled boolean false Whether the select is disabled.
required boolean false Whether the select is required.
clearable boolean false Whether a clear button appears when a value is selected.
maxDropdownHeight number 240 Maximum height of the dropdown in pixels.
minDropdownWidth number Minimum width of the dropdown in pixels. The dropdown still grows past this to fit its content.
fullWidth boolean false Whether the select stretches to fill the width of its container.
name string Name attribute for hidden input (form submission).
onChange (value: T) => void Callback when the selected value changes. The value type is discriminated by `clearable`: a non-clearable select emits `T`; a `clearable` select emits `T | null` (null comes from the clear button).
onOpenChange (open: boolean) => void Callback when the dropdown open state changes.
className string Additional CSS class names.
style CSSProperties Inline styles.
testId string Test identifier for automated testing.
ref Ref<HTMLButtonElement> Ref to the trigger button element.

| Prop | Type | Description | | ---------- | ----------- | ---------------------------------------- | | value | T | Unique value used for selection. | | label | string | Display label (falls back to value). | | icon | ReactNode | Optional icon rendered before the label. | | disabled | boolean | Whether this option is disabled. |

| Prop | Type | Description | | --------- | ----------------------- | ------------------------------------ | | label | string | Group header label (non-selectable). | | options | SelectOptionItem<T>[] | Options within this group. |

  • Trigger renders as a <button> with role="combobox" and aria-haspopup="listbox"
  • Dropdown renders as a role="listbox" with role="option" children
  • Grouped options use role="group" with aria-label
  • Full keyboard navigation: Arrow Up/Down to navigate, Enter to select, Escape to close, Home/End for first/last
  • aria-expanded reflects the dropdown open state
  • aria-selected marks the current selection
  • aria-invalid is set when in error state
  • aria-required is set when the select is required
  • aria-describedby links to helper/error text
  • Clear button has aria-label="Clear selection"