esc

Type to search...

The problem

Search filtering sounds simple until you have real users. Some type the exact name. Some type the first few letters. Some type a word from the middle. Some type acronyms. A rigid exact-match filter breaks for most of them.

On the performance side, filtering 100k+ items on every keystroke can freeze the UI if you are not careful. The library handles both problems: flexible match strategies via match-sorter and optional debouncing to keep things smooth.

Filter types

The filterType prop controls how aggressively the search matches. It maps directly to match-sorter ranking thresholds. The default is CONTAINS, which matches anywhere in the string.

Available filter types

Prop Type Default Description
CASE_SENSITIVE_EQUAL Ranking Exact match, case-sensitive. 'London' matches 'London' but not 'london'.
EQUAL Ranking Exact match, case-insensitive. 'london' matches 'London'.
STARTS_WITH Ranking Matches from the start. 'lon' matches 'London' but not 'Babylon'.
WORD_STARTS_WITH Ranking Any word can match from its start. 'yor' matches 'New York'.
CONTAINS Ranking Matches anywhere in the string. 'ond' matches 'London'. This is the default.
ACRONYM Ranking Matches against first letters of words. 'ny' matches 'New York'.
MATCHES Ranking Most permissive. Matches individual characters in order.
NO_MATCH Ranking Nothing matches. Useful to disable filtering temporarily.
StartsWithExample.tsx
<SearchableDropdown
  options={cities}
  value={city}
  setValue={setCity}
  filterType="STARTS_WITH"
  placeholder="Type the first letters..."
/>

Search option keys

When your options are objects, searchOptionKeys tells the filter which fields to search against. It defaults to ["label"] for object options, so basic search works out of the box. Pass additional keys to search more fields.

SearchKeysExample.tsx
type Product = {
  label: string;
  value: string;
  category: string;
  sku: string;
};

// Works without searchOptionKeys — searches "label" by default
<SearchableDropdown
  options={products}
  value={product}
  setValue={setProduct}
  placeholder="Search products..."
/>

// Search by name and category, but not by SKU
<SearchableDropdown
  options={products}
  value={product}
  setValue={setProduct}
  searchOptionKeys={["label", "category"]}
  placeholder="Search products..."
/>

The type system ensures searchOptionKeys only accepts keys that exist on your option type. If you add a field to your type later, it becomes available as a search key automatically. If you remove one that's referenced, TypeScript catches it.

Debouncing

For small option lists (under a few thousand), filtering is instant and debouncing is unnecessary. For very large lists (100k+), debouncing prevents the filter from running on every keystroke.

DebounceExample.tsx
// No debounce (default) — good for small lists
<SearchableDropdown
  options={smallList}
  value={value}
  setValue={setValue}
  debounceDelay={0}
/>

// 100ms debounce — good for large lists
<SearchableDropdown
  options={hugeList}
  value={value}
  setValue={setValue}
  debounceDelay={100}
/>

Create new option

When the search query doesn't match any option, the dropdown shows a "no match" entry by default. If createNewOptionIfNoMatch is true (the default), selecting that entry creates a new option from the search text.

NoCreateExample.tsx
// Disable new option creation
<SearchableDropdown
  options={cities}
  value={city}
  setValue={setCity}
  createNewOptionIfNoMatch={false}
  dropdownOptionNoMatchLabel="Nothing found"
/>

Next steps