CwSearchInput

Debounced async search with AbortController, arrow-key navigation.

CwSearchInput example Copy code
<CwSearchInput
	label="Search fruits"
	placeholder="Type at least 3 charsโ€ฆ"
	fetchSuggestions={fetchSuggestions}
	bind:value={value}
/>
Documentation Upgrade

Start here

CwSearchInput is for async suggestion search. It handles debouncing, cancellation, keyboard navigation, and selected-value binding so you can focus on fetching and mapping results.

How to think about it

  1. Return suggestions from `fetchSuggestions` The callback receives both the query string and an `AbortSignal`. Respect the signal so old requests can be cancelled cleanly.
  2. Map objects into labels and values For simple string arrays the defaults are enough. For object results, provide `mapSuggestionToLabel` and `mapSuggestionToValue`.
  3. Separate typing from selection Use `oninput` to respond to free typing and `onselect` when the user actually chooses a suggestion.

Props and callbacks

Native field props such as `name`, `required`, `autocomplete`, and `class` are also supported.

APITypeDetails
value

Default: ''

stringCurrent text value. Bind this when the parent owns the field state.
fetchSuggestions
(query: string, signal: AbortSignal) => Promise<unknown[]>Async loader for the suggestion list.
minChars

Default: 3

numberMinimum characters before fetching suggestions.
debounceMs

Default: 250

numberDelay before triggering `fetchSuggestions`.
mapSuggestionToLabel
(item: unknown) => stringConverts a result item into visible list text.
mapSuggestionToValue
(item: unknown) => stringConverts a result item into the bound field value.
label / placeholder
stringOptional field label and empty-state prompt.
clearable

Default: true

booleanShows a clear button when the field has content.
disabled

Default: false

booleanDisables typing and selection.
oninput
(value: string) => voidRuns whenever the raw text changes.
onselect
(value: string, item: unknown) => voidRuns when the user chooses a suggestion.

Copy-paste examples

These snippets intentionally show the full public API surface the live demo relies on.

Search over a simple string list

The default mapping helpers are enough when your suggestions are already strings.

Search over a simple string list Copy code
<script lang="ts">
	const crops = ['Tomato', 'Pepper', 'Lettuce', 'Basil'];
	let value = $state('');

	async function fetchSuggestions(query: string, signal: AbortSignal) {
		await new Promise((resolve) => setTimeout(resolve, 250));
		if (signal.aborted) throw new DOMException('Aborted', 'AbortError');
		return crops.filter((crop) => crop.toLowerCase().includes(query.toLowerCase()));
	}
</script>

<CwSearchInput
	label="Search crops"
	minChars={1}
	fetchSuggestions={fetchSuggestions}
	bind:value={value}
/>
Search object results with custom mapping

Use this pattern when the API returns structured objects.

Search object results with custom mapping Copy code
<script lang="ts">
	let selectedId = $state('');

	async function fetchDevices(query: string, signal: AbortSignal) {
		const response = await fetch('/api/devices?q=' + encodeURIComponent(query), { signal });
		return await response.json();
	}
</script>

<CwSearchInput
	label="Search devices"
	name="device"
	placeholder="Type device name or serial..."
	fetchSuggestions={fetchDevices}
	mapSuggestionToLabel={(item) => `${item.name} (${item.serial})`}
	mapSuggestionToValue={(item) => item.id}
	oninput={(value) => console.log('Typing:', value)}
	onselect={(value, item) => {
		selectedId = value;
		console.log('Selected device:', item);
	}}
/>