YuhuanStudioYunUIDocs
Forms & Inputs

CustomSelect

Fully custom-styled select with optional search, icons, descriptions and keyboard support.

A fully custom-styled dropdown select with optional in-dropdown search, per-option icons and descriptions, and complete keyboard support (arrows, Home/End, Enter, Escape). It's controlled: pass value and onChange, plus an options array. For free-text entry or creatable values see Combobox; for the Radix-based composed parts see Select.

Import

tsx
1import { CustomSelect } from "@yuhuanowo/yunui";

Usage

CustomSelect is controlled — wire value and onChange to state:

tsx
1const [value, setValue] = useState("medium");
2 
3<CustomSelect
4 value={value}
5 onChange={setValue}
6 searchable
7 placeholder="Select a model size"
8 options={[
9 { value: "small", label: "Small", description: "Fast, lower cost" },
10 { value: "medium", label: "Medium", description: "Balanced" },
11 { value: "large", label: "Large", description: "Highest quality" },
12 { value: "xl", label: "Extra Large", description: "Maximum capacity" },
13 ]}
14/>

Example

Set searchable to add a filter box inside the dropdown. Each option can carry an icon and a secondary description line.

Searchable select with descriptions

By default searchable filters the given options client-side. Pass onSearch to drive results from a backend instead: it fires (debounced by searchDebounceMs, default 250) with the current query, and you replace options with the fetched results. Pair it with loading to surface the in-flight fetch.

tsx
1const [options, setOptions] = useState<CustomSelectOption[]>([]);
2const [loading, setLoading] = useState(false);
3 
4<CustomSelect
5 searchable
6 value={value}
7 onChange={setValue}
8 options={options}
9 loading={loading}
10 onSearch={async (query) => {
11 setLoading(true);
12 setOptions(await searchModels(query));
13 setLoading(false);
14 }}
15/>

Infinite scroll

For long or paged result sets, pass onLoadMore + hasMore. onLoadMore fires when the list is scrolled near its end (and no fetch is in flight); append the next page to options. It's independent of onSearch — use either or both.

tsx
1<CustomSelect
2 value={value}
3 onChange={setValue}
4 options={options}
5 hasMore={hasMore}
6 onLoadMore={() => loadNextPage()}
7/>

Props

PropTypeDefaultDescription
classNamestring
disabledbooleanfalseDisable interaction and dim the control.
hasMorebooleanfalseWhether more options can be loaded; gates `onLoadMore` and the footer spinner.
loadingbooleanfalseIn remote mode, show a loading indicator while results are being fetched.
onChange*(value: string) => voidCalled with the chosen option's value.
onLoadMore(() => void)Infinite scroll. Called when the options list is scrolled near its end and `hasMore` is true (and no fetch is in flight). The host appends the next page to `options`. Independent of `onSearch` — use either or both.
onSearch((query: string) => void)Server-backed search. When provided, the component switches to remote mode: it stops filtering `options` locally (the host owns filtering) and instead calls `onSearch` — debounced by `searchDebounceMs` — with the current query so the host can fetch matching options. A search box is shown even without `searchable`. Pair with `loading` to surface the in-flight fetch.
options*SelectOption[]The selectable options.
placeholderstringPlaceholder shown when nothing is selected (falls back to i18n default).
searchablebooleanfalseShow an in-dropdown search box that filters options by label/value.
searchDebounceMsnumber250Debounce (ms) before `onSearch` fires.
value*stringCurrently selected value (controlled).

On this page