Config
createSelkit(config) and every adapter accept a SelkitConfig. All options are optional.
SelkitConfig
| Option | Type | Default | Description |
|---|---|---|---|
options | SelkitItem<T>[] | [] | Initial options — flat options or groups. |
value | SelkitValue | null | Initial selection. |
multiple | boolean | false | Allow multiple selection. |
searchable | boolean | true | Whether the list can be searched. |
minResultsForSearch | number | 0 | Show the search box only once the option count reaches this. |
minInputLength | number | 0 | Minimum characters before filtering / loading. |
fuzzy | boolean | false | Use fuzzy subsequence matching instead of substring. Ignored if filter is set. |
filter | FilterFn<T> | substring | Custom match predicate (option, query) => boolean. |
sorter | SorterFn<T> | — | Custom result ordering (a, b, query) => number (e.g. relevance). Flat lists only — ignored when options are grouped. Also a Vue/React prop. |
hideSelected | boolean | false | Remove chosen options from the list. |
clearable | boolean | true single | Show a clear button. |
closeOnSelect | boolean | true single / false multiple | Close the dropdown after selecting. |
disabled | boolean | false | Disable the control. |
placeholder | string | — | Placeholder text. |
ariaLabel | string | — | Accessible name (aria-label) for the search input. Falls back to placeholder. Set one of them so screen readers (and axe) can identify the field. |
loadOptions | (query, page, { signal }) => Promise<SelkitItem<T>[] | SelkitLoadResult<T>> | — | Async / paginated loading. The signal is aborted when superseded. See Async. |
debounce | number | 250 | Debounce in ms for loadOptions. |
filterRemote | boolean | false | Apply the local filter to remote results. |
cache | boolean | false | Memoize the first page of loadOptions results by query. First page only; cleared by setOptions. See Async › Caching. |
cacheTTL | number | — | Milliseconds before a cache entry is stale. Omit = never expires. Only with cache: true. |
resolveSelected | (values) => SelkitOption<T>[] | Promise<SelkitOption<T>[]> | — | Look up full options for initial values whose options aren't loaded yet, so their labels can render. Sync or async; runs once at init; resolved options only fill selected labels (not the option pool). See Async › Prefilling. |
taggable | boolean | false | Allow creating options on the fly. |
createTag | (query) => SelkitOption<T> | — | Factory for new tags. |
isValidToken | (query) => boolean | — | Gate tag creation. Returning false silently hides the create row and blocks Enter / token separators. See Tagging › Validating. |
tokenSeparators | string[] | [] | Auto-split typed/pasted input on these separators (e.g. [',', ' ']) into tags. Multiple-select only; creating new tags also needs taggable. Tokens matching an existing option are selected; the remainder stays in the input. |
restoreOnBackspace | boolean | false | Multiple-select only: when the input is empty, Backspace removes the last tag and restores its label to the input for editing (and opens the dropdown). |
maxSelections | number | — | Cap on selected items. |
messages | Partial<SelkitMessages> | English defaults | Override the empty-state messages (loading / no results / type-more). See i18n. |
i18n / messages
The dropdown's empty-state text is resolved by the core via controller.getEmptyMessage(), so every adapter shows the same string. Override any subset of the three messages — unspecified keys keep their English default.
createSelkit({
minInputLength: 3,
messages: {
loading: '載入中…',
noResults: '查無資料',
// `remaining` is how many more characters are still needed
minInputLength: (remaining) => `再輸入 ${remaining} 個字`,
},
})getEmptyMessage() picks one message based on the current state, in order:
loading— while asyncloadOptionsis in flight.minInputLength(remaining)— when the query is shorter thanminInputLength.noResults— otherwise (no matching options).
create(query) is separate: it labels the visible "create new item" row shown in the list when taggable is on (see Selection › Tagging).
interface SelkitMessages {
loading: string
noResults: string
minInputLength: (remaining: number) => string
create: (query: string) => string // defaults to `Add "${query}"`
// aria-live announcements (read by screen readers)
selected: (label: string) => string // `${label} selected`
deselected: (label: string) => string // `${label} removed`
cleared: () => string // 'Selection cleared'
resultsCount: (count: number) => string //'N results available'
}The selected / deselected / cleared / resultsCount keys label the live-region announcements for screen readers.
DOM-only options
SelkitDomConfig (used by @selkit/dom) extends the above:
| Option | Type | Default | Description |
|---|---|---|---|
classPrefix | string | "selkit" | BEM class prefix. |
name | string | — | Maintain hidden inputs for form submission. |
checkboxes | boolean | false | Multiple-select only: show a checkbox tick on each option (adds the selkit--checkboxes modifier; styling lives in the theme). Also a Vue/React prop. |
autogrow | boolean | false | Grow the search input to fit its text (via the size attribute) instead of stretching to fill. Adds the selkit--autogrow modifier. Also a Vue/React prop. |
dropdownAutoWidth | boolean | false | Size the dropdown to its content (at least as wide as the control, wider if needed) instead of matching the control width. Adds the selkit--auto-width modifier. Also a Vue/React prop. |
virtualScroll | boolean | false | Render only the visible slice. |
itemHeight | number | 36 | Fixed row height for virtual scroll. |
dropdownParent | HTMLElement | string | — | Portal the dropdown into another element to escape clipping ancestors. Also a Vue/React prop. |
positioner | PositionerFactory | built-in | Swap the dropdown positioner. Pass createFloatingPositioner from @selkit/floating for flip / shift / size collision handling. Also a Vue/React prop. See Positioning. |
templateOption | (option, meta) => string | Node | — | Customize a dropdown option's content. Strings are set as text; return a Node for markup (icons). meta is { index, active, selected }. DOM-only — see renderOption / option slot for Vue/React. |
templateSelection | (option, meta) => string | Node | — | Customize the selected tag / single-value content. Strings are set as text; return a Node for markup (icons). meta is { index, multiple }. DOM-only — see renderSelection / selection slot for Vue/React. |
The Vue and React components expose the same options as props, plus virtualScroll / itemHeight and framework-specific bits. Customize a dropdown option with renderOption (React) / the option slot (Vue) / templateOption (DOM), and the selected display with renderSelection (React) / the selection slot (Vue) / templateSelection (DOM). Option meta is { index, active, selected }; selection meta is { index, multiple }.
Swappable components
Beyond option/selection content, you can replace the content of the structural chrome — the dropdown arrow, clear button, tag remove button, group heading and the empty/loading row. Each adapter uses its idiomatic mechanism, and only the inner content is swapped — the wrappers, classes and behavior (click handling, event delegation) stay intact, so you can't accidentally break the clear/remove buttons.
| Part | DOM config | Vue slot | React prop | Meta |
|---|---|---|---|---|
| Dropdown arrow | templateArrow | arrow | renderArrow | { open } |
| Clear button | templateClear | clear | renderClear | — |
| Tag remove button | templateTagRemove | tag-remove | renderTagRemove | (option, { index }) |
| Group heading | templateGroup | group | renderGroup | { label, disabled } |
| Empty / loading | templateEmpty | empty | renderEmpty | { reason, message, query } |
DOM hooks return string | Node (strings are set as text for XSS safety, Nodes are appended); Vue slots and React props return their native node types. The empty hook's reason is 'loading' \| 'min-input' \| 'no-results' and message is the resolved default text — branch on reason to show a spinner while loading and fall back to message otherwise. These are presentation-only, so they live entirely in the adapters (not in @selkit/core).
Types
interface SelkitOption<T = unknown> {
value: string | number
label: string
disabled?: boolean
data?: T
}
interface SelkitGroup<T = unknown> {
label: string
disabled?: boolean
options: SelkitOption<T>[]
}
type SelkitItem<T = unknown> = SelkitOption<T> | SelkitGroup<T>
type SelkitValue = string | number | null | Array<string | number>
type FilterFn<T = unknown> = (option: SelkitOption<T>, query: string) => boolean
type SorterFn<T = unknown> = (
a: SelkitOption<T>,
b: SelkitOption<T>,
query: string,
) => number
interface SelkitLoadResult<T = unknown> {
items: SelkitItem<T>[]
hasMore: boolean
}