Skip to content
llms.txt
llms.txt

Query — fuzzy search

The fuzzy Query demo keeps its data local and searches it with createFuzzySearchSource(). Use the implementation switch in the preview to compare the Web Components and Vue adapters with the same search contract.

Live demo

Query · fuzzy search

Source for this preview

FuzzyComboboxExample.astro
---
import {multilingualSearchPrompts} from '../../multilingual-destinations';
---
<article class="demo-card combobox-card">
<div class="card-top">
<span class="number">F</span>
<span class="chip">search sources</span>
</div>
<section
class="combobox-panel"
id="combobox-demo"
>
<h3>Multilingual query</h3>
<p>Search by city, country, local script, alias, or forgiving typo.</p>
<div class="combobox-shell">
<label class="combobox-label" for="destination-search">
Destination
</label>
<floating-root id="combobox-root" placement="bottom-start">
<floating-list navigation loop allow-escape>
<floating-query
id="combobox-query"
query-trigger-selector="#combobox-hints .search-sample"
>
<floating-reference>
<input
id="destination-search"
class="combobox-input"
type="text"
autocomplete="off"
spellcheck="false"
aria-describedby="combobox-hints combobox-status"
placeholder="Search city or country…"
/>
</floating-reference>
<template slot="content">
<floating-results
class="combobox-popup"
aria-label="Destination suggestions"
>
<floating-results-status type="loading">
<div
class="combobox-empty"
role="option"
aria-disabled="true"
>
Searching…
</div>
</floating-results-status>
<floating-results-status type="error">
<div
class="combobox-empty"
role="option"
aria-disabled="true"
>
Search failed.
</div>
</floating-results-status>
<floating-results-status type="empty">
<div
class="combobox-empty"
role="option"
aria-disabled="true"
>
No destination found for “<span
data-search-text="$query"
></span>”
</div>
</floating-results-status>
<floating-results-item>
<floating-list-item>
<div class="combobox-option">
<span>
<strong data-search-text="label"></strong>
<small data-search-text="region"></small>
</span>
<span
class="language-badge"
data-search-text="language"
></span>
</div>
</floating-list-item>
</floating-results-item>
</floating-results>
</template>
<p
id="combobox-status"
class="sr-only"
aria-live="polite"
>
Destination suggestions closed
</p>
</floating-query>
</floating-list>
</floating-root>
<span class="combobox-icon" aria-hidden="true">⌕</span>
</div>
<div id="combobox-hints" class="combobox-hints">
{
multilingualSearchPrompts.map(([sample, destination]) => (
<button type="button" value={sample} class="search-sample">
<code>{sample}</code><span>→ {destination}</span>
</button>
))
}
</div>
<code>
&lt;floating-query&gt; + &lt;floating-results&gt; +
&lt;floating-list-item&gt;
</code>
</section>
</article>
<script>
import {
FloatingRootElement,
createFuzzySearchSource,
dismiss,
flip,
offset,
size,
shift,
type FloatingQueryElement,
} from '@floating-ui-plus/web-components';
import {
multilingualDestinations,
multilingualSearchKeys,
type MultilingualDestination,
} from '../../multilingual-destinations';
import {initializeExample} from './initialize-example';
initializeExample('combobox', (scope) => {
const floating = FloatingRootElement.query(
scope,
'#combobox-root',
);
const query = scope.querySelector<FloatingQueryElement>(
'#combobox-query',
);
if (!query) return;
query.configure<MultilingualDestination>({
search: {
source: createFuzzySearchSource(multilingualDestinations, {
keys: multilingualSearchKeys,
threshold: 0.35,
}),
getItemKey: (item) => item.id,
debounceMs: 0,
},
getItemLabel: (item) => item.label,
status: {
closed: 'Destination suggestions closed',
idle: 'Start typing to search',
loading: 'Searching destinations',
error: 'Destination search failed',
empty: ({search: state}) =>
`No destinations found for ${state.query}`,
results: ({search: state}) =>
`${state.items.length} destinations available`,
},
});
const activate = (event: Event) => {
const item = (
event as CustomEvent<{item: MultilingualDestination}>
).detail.item;
const input = scope.querySelector<HTMLInputElement>(
'#destination-search',
);
if (input) input.value = item.label;
};
query.addEventListener('queryactivate', activate);
floating.configure({
middleware: [
offset(8),
flip({padding: 18}),
shift({padding: 18}),
size({
padding: 18,
rootBoundary: 'viewport',
apply({availableHeight, elements}) {
const maxHeight = `${Math.max(0, availableHeight)}px`;
elements.floating.style.setProperty(
'--combobox-popup-max-height',
maxHeight,
);
elements.floating.style.maxHeight = maxHeight;
},
}),
],
plugins: [dismiss()],
});
return () => query.removeEventListener('queryactivate', activate);
});
</script>

Open the full demo ↗

  • Local typo-tolerant matching across city, country, aliases, and local scripts.
  • Query presets, virtual focus, keyboard navigation, and an ARIA live region.
  • Activation callbacks, virtual focus, and default ARIA combobox semantics.
  • The same createSearch()/useSearch() and createQuery()/useQuery() phases in both renderers.

The search controller owns query, loading, error, empty, results, IME, cancellation, stale-response protection, and pagination state. The renderer owns the result markup and accessible names.

query.configure({
search: {
source: createFuzzySearchSource(destinations, {keys: searchKeys}),
getItemKey: (item) => item.id,
},
getItemLabel: (item) => item.label,
});

Use getItemLabel() for visible copy. The usage recipes cover controlled search state, activation handlers, and renderer-specific composition. Use the deprecated Combobox API only for native selected-value form submission.