- Docs
- Autocomplete
Autocomplete
Build searchable autocomplete UIs with rich selected content, empty states, and multi selection.
@flexilla/autocomplete builds on top of @flexilla/select-core and connects the search input, option list, and selected values in the DOM. Use it when you want:
- typed search over a list of options
- single or multiple selection
- rich selected content or tags
- a headless DOM binding without a framework dependency
Installation
Markup structure
Autocomplete does not need a wrapper. The input, content, and value elements only need to share the same id.
<input
data-fx-autocomplete
data-select-input
data-autocomplete-id="framework-autocomplete"
type="text"
placeholder="Search frameworks"
/>
<div
data-select-content
data-select-id="framework-autocomplete"
data-placement="bottom-start"
class="ui-popper"
hidden
>
<button data-select-item="astro">Astro</button>
<button data-select-item="vue">Vue</button>
<button data-select-item="laravel">Laravel</button>
</div>
<div data-selected-value data-select-id="framework-autocomplete"></div>
<button data-select-clear data-select-id="framework-autocomplete">Clear</button> DOM attributes
| Attribute | Role |
|---|---|
data-select-input | Search input that drives the autocomplete |
data-select-trigger | Optional button that opens/closes the panel |
data-select-item | Selectable option (value goes in the attribute) |
data-selected-value | Container that renders the current selection |
data-select-clear | Resets all selected values |
data-select-clear-all | Same as data-select-clear |
data-select-remove | Removes a specific value (data-select-remove="value") |
data-autocomplete-id | Shared id used to link input, content, and value elements |
data-fx-autocomplete | Auto-init selector (placed on the input) |
Overlay attributes
Put these on the content element to control positioning:
| Attribute | Default | Description |
|---|---|---|
data-placement | bottom-start | Popper placement |
data-offset-distance | 6 | Gap between input and panel |
data-prevent-close-outside | false | Keep open when clicking outside |
data-prevent-close-inside | true (with multiple) | Keep open when clicking inside |
data-readjust-height | true | Recalculate position on scroll |
data-min-height | 140 | Minimum panel height |
readjustHeight is enabled by default for autocomplete. Disable it with data-readjust-height="false" or { readjustHeight: false }. The panel receives
--trigger-width, so you can match the input width: [data-select-content] {
width: var(--trigger-width);
} Basic usage
Class API
import { Autocomplete } from "@flexilla/autocomplete";
new Autocomplete('[data-select-input][data-autocomplete-id="framework-autocomplete"]'); You can also pass the input element directly.
The constructor expects a CSS selector or
HTMLElement. It reads the id from that element and resolves the rest of the autocomplete automatically. Auto init
import { Autocomplete } from "@flexilla/autocomplete";
Autocomplete.autoInit(); Looks for
[data-fx-autocomplete] elements and initializes them. Controller API
import { createAutocomplete } from "@flexilla/autocomplete";
const autocomplete = createAutocomplete();
const { destroy } = autocomplete.connect({
element: '[data-select-input][data-autocomplete-id="framework-autocomplete"]',
}); You can also connect with an existing element:
const input = document.querySelector('[data-autocomplete-id="framework-autocomplete"]');
autocomplete.connect({ element: input }); Search and filtering
The input is the main trigger. The panel opens on focus or click, and filtering happens as the user types.
Customize the filter:
createAutocomplete({
filter: (query, item) => {
return item.label?.toLowerCase().startsWith(query.toLowerCase()) ?? false;
},
}); Search debounce (default 100ms):
createAutocomplete({
searchDebounce: 250, // ms
}); Set to
0 for immediate filtering on every keystroke. Selected value templates
data-selected-value can define a custom template with template[data-selected-model]. Template bindings:
| Attribute | Description |
|---|---|
data-bind | Injects text from item data |
data-bind-src | Sets src attribute |
data-bind-alt | Sets alt attribute |
data-bind-title | Sets title attribute |
data-bind-href | Sets href attribute |
data-bind-style | Sets inline styles from item data |
data-select-label | Injects the item label |
data-select-value | Injects the raw value |
data-select-remove | Adds a remove button (multiple mode) |
<div data-selected-value data-select-id="owners">
<template data-selected-model>
<span class="inline-flex items-center gap-2">
<span
class="size-5 rounded-full"
data-bind-style="badgeStyle"
data-bind="initials"
></span>
<span data-bind="label"></span>
</span>
</template>
<span data-placeholder>No owner selected</span>
</div>
<li
data-select-item="brenda"
data-label="Brenda Stone"
data-initials="BS"
data-badge-style="background-color:#fecdd3;color:#881337"
>
...
</li> Multiple selection
Enable with the
multiple option: new Autocomplete('[data-select-input][data-autocomplete-id="team-members"]', { multiple: true }); Or in markup:
<input data-fx-autocomplete data-multiple data-select-input data-autocomplete-id="team-members" type="text" /> In multiple mode:
- selecting items keeps the panel open
- removing a selected item also keeps the panel open
- the input remains available for continued search
Empty state
Rendered when no item matches the query.
<template data-select-empty>
<div>
No owner found for "<span data-select-empty-query></span>"
</div>
</template> State and methods
Like Select, the controller exposes select-core methods directly:
open() close() toggle() select(value) unselect(value) clear() toggleValue(value) highlight(index) highlightNext() highlightPrev() setSearch(query) getState() subscribe(listener) autocomplete.subscribe((state) => {
console.log(state.search, state.selectedValues);
}); Lifecycle
Class API:
const ac = new Autocomplete('...');
ac.cleanup(); Controller API:
const { destroy } = autocomplete.connect({ element: '...' });
destroy(); Autocomplete vs Select
- Use Autocomplete when interaction starts from typing.
- Use Select when interaction starts from a trigger button and option panel.
- Use Select Core when you want the state engine without DOM bindings.