UnflowUI
Components

Select

A form select with a custom trigger, searchable dropdown, and multiselect support — backed by a hidden native `<select>` for full form-library compatibility.

Overview

Select renders a button-style trigger plus a searchable option list, while keeping a visually hidden native <select> in sync underneath — so it works with native form submission, required validation, and libraries like React Hook Form or Formik without extra wiring.

The option list itself is rendered by SelectDropdown, the lower-level primitive that owns search filtering, keyboard focus, and single/multiselect item rendering. Reach for SelectDropdown directly only if you need a dropdown list detached from a trigger button; for a standard form field, use Select.

Import

import { Select } from '@unflow.io/ui/components/Select';

Props

PropTypeDefaultDescription
optionsSelectDropdownItem[]Required. { label, value, description?, startIcon?, className? }[] rendered as rows
valuestring | string[]Controlled selected value(s). Use a string[] when multiselect
defaultValuestring | string[]Uncontrolled initial value(s)
onChange(event: ChangeEvent<HTMLSelectElement>) => voidFired on the hidden native <select> on every change — read event.target.value (or .selectedOptions for multiselect)
multiselectbooleanfalseAllows selecting multiple options, shown as removable tags under the trigger
label / hintstringField label and helper text, rendered via InputLabel
placeholderstringi18n select.placeholderTrigger text shown while nothing is selected
required / disableRequiredSymbolbooleanMarks the field required; the second hides the "*" marker
disabledbooleanfalseDisables the trigger, native select, and tag removal
fullWidthbooleanTrigger and tag row span 100% of the container width
status / statusMessage'valid' | 'invalid' | 'error', stringValidation state and message shown inline. Falls back to 'error' automatically when the native select fails HTML validation (e.g. required)
namestringname of the hidden native <select>, used for form submission
defaultOpenbooleanfalseDropdown open on initial mount
keepOpenbooleanKeeps the dropdown open after picking an option (single select only — multiselect always stays open until click-away)
disableSearchbooleanHides the search input inside the dropdown
controlledSearchstringControlled search query — pair with onSearchChange
onSearchChange / onSearchClear(event) => voidFired when the search input changes / its clear button is pressed
maxVisibleRowsnumber5Number of option rows visible before the list scrolls
rowHeightnumberFixed row height in px
actionsIActionCard[]Extra actions rendered below the option list (e.g. "Clear selection")

Basic usage

const [value, setValue] = useState('');

<Select
  name="fruit"
  label="Favorite fruit"
  placeholder="Choose a fruit"
  value={value}
  onChange={(e) => setValue(e.target.value)}
  options={[
    { label: 'Apple', value: 'apple' },
    { label: 'Banana', value: 'banana' },
    { label: 'Cherry', value: 'cherry' },
    { label: 'Dragon fruit', value: 'dragon-fruit' },
    { label: 'Elderberry', value: 'elderberry' },
  ]}
/>

Multiselect

Selected options render as removable tags below the trigger. value/onChange deal in string[] while multiselect is set.

const [value, setValue] = useState<string[]>([]);

<Select
  name="assignees"
  label="Assignees"
  placeholder="Assign teammates"
  multiselect
  value={value}
  onChange={(e) => setValue(Array.from(e.target.selectedOptions, (o) => o.value))}
  options={[
    { label: 'Ana Silva', value: 'ana' },
    { label: 'Bruno Costa', value: 'bruno' },
    { label: 'Carla Dias', value: 'carla' },
    { label: 'Diego Alves', value: 'diego' },
  ]}
/>

With icons

Each option can carry a startIcon, shown both in the option row and next to the trigger label once selected.

const [value, setValue] = useState('cloud');

<Select
  name="integration"
  label="Integration"
  value={value}
  onChange={(e) => setValue(e.target.value)}
  options={[
    { label: 'Cloud sync', value: 'cloud', startIcon: <Cloud size={14} /> },
    { label: 'Favorites', value: 'favorites', startIcon: <Heart size={14} /> },
  ]}
/>

Validation status

Pass status/statusMessage to surface inline validation feedback. The same error state is applied automatically when the hidden native select fails HTML validation (e.g. a required field submitted empty).

<Select
  name="plan"
  label="Plan"
  required
  status="error"
  statusMessage="Please select a plan to continue."
  options={[
    { label: 'Starter', value: 'starter' },
    { label: 'Pro', value: 'pro' },
    { label: 'Enterprise', value: 'enterprise' },
  ]}
/>

Disabled

<Select
  name="region"
  label="Region"
  disabled
  defaultValue="eu"
  options={[
    { label: 'Europe', value: 'eu' },
    { label: 'United States', value: 'us' },
  ]}
/>

Customization (slots & slotProps)

SlotTypeDescription
triggerIconReactNodeOverrides the caret icon on the trigger
tag(option: SelectDropdownItem) => ReactNodeCustom render for each selected multiselect tag
Slot PropPropsDescription
triggerOmit<BaseButtonProps, ...> & { startEl?: ReactNode }The trigger <button>; startEl overrides the leading icon
triggerLabelBaseHTMLProps<HTMLParagraphElement>The trigger's label text
triggerStartElRoot / triggerIconRootBaseHTMLProps<HTMLDivElement>Wrapper divs for the leading icon / caret icon
statusOmit<IInputStatus, 'status' | 'message'>The inline validation indicator
tagsRoot / tagBaseHTMLProps<HTMLDivElement> / Omit<ITag, 'label'>Tags row wrapper / each selected-option tag (multiselect)
dropdownOmit<ISelectDropdown, ...>Forwarded to the underlying SelectDropdown — search input, list, and actions rendering
inputLabelOmit<IInputLabel, ...>Forwarded to the wrapping InputLabel

Accessibility

  • The trigger exposes aria-haspopup="listbox", aria-expanded, and aria-controls pointing at the dropdown's listbox, plus aria-invalid/aria-errormessage when a validation message is shown.
  • ArrowUp/ArrowDown on the closed trigger opens the dropdown, matching native <select> keyboard behavior.
  • The hidden native <select> mirrors every selection change (dispatching real input/change events), so browser validation, autofill, and form libraries all see a standard select element.