Autocomplete keeps the original suggestion dataset and the selected display label while the user edits. Escape and Tab in the input close the menu and cancel a pending open. Listbox options stay out of the sequential Tab order.
isOpen follows the suggestion menu through rerenders and dismissal. When the selection changes, the input emits a bubbling, composed change event, so delegated form listeners can observe it.
Autocomplete adds a visually hidden, polite live region for loading, result counts and multi-selection changes. The visible spinner and count stay unchanged. It announces only while the input has focus and its text meets minLength. Closing the menu clears the region. Check spoken delivery with a screen reader.
Override i18n.loading and i18n.noResults, which default to "Loading results." and "No results.". For localized plurals, supply i18n.results(count) and i18n.selected(count) functions. The defaults produce "1 result available." or "N results available." and "1 item selected." or "N items selected.". i18n.create(text) labels the "Add" row and defaults to Add "text". Missing keys keep their defaults. All messages are plain text.
When a search returns no suggestions, the open list also shows i18n.noResults as its first row, so sighted users see what the live region says. The row is not an option. It is hidden from assistive technology, arrow keys skip it, and a click on it chooses nothing and leaves focus in the field. The row goes away when a search returns a match. In Chips, pressing Enter to add the typed text as a chip closes the list.
Updates announce the count passed to setMenuItems, excluding fallback selected items, disabled entries and the "Add" row. Opening an existing list announces its displayed item count, without disabled entries. Updates with open: false stay silent. Your application handles request cancellation, stale-response ordering and errors. The plugin ignores results delivered after destroy(), and results delivered after the user leaves the field update the list without opening it.
Add autocomplete to a text input inside .field.
AutoInit() starts every .autocomplete except
those with no-autoinit. The default data list is empty,
so pass options, or call init, to give it something to suggest.
Set isMultiSelect: true to pick several values. A count appears on the field.
Set disabled: true on an entry to show it without letting anyone choose it. Oslo is unavailable, and Vienna is selected and cannot be removed.
Set group on entries to list them under headings.
<div class="field">
<span class="material-symbols prefix" aria-hidden="true">textsms</span>
<input type="text" id="autocomplete-input" class="autocomplete" placeholder=" ">
<label for="autocomplete-input">Autocomplete</label>
</div>
Initialization
The IIFE bundle exposes Expressive.Autocomplete.
data is an array of objects:
-
id (required). A string or number, used as the option text
when
textis omitted. -
text. The display label. The default search matches
idandtext. - image. An image URL, shown as a circle. The default search ignores it.
- description. An optional secondary line under the label.
-
disabled.
truekeeps the entry in the list, dimmed, but nobody can choose it. - group. An optional heading to list the entry under.
The default search ignores extra properties. Filter them yourself in
onSearch.
document.addEventListener('DOMContentLoaded', function() {
const elems = document.querySelectorAll('.autocomplete');
Expressive.Autocomplete.init(elems, {
minLength: 0,
data: [
{ id: 12, text: 'Apple' },
{ id: 13, text: 'Microsoft' },
{ id: 42, text: 'Google', image: 'https://picsum.photos/id/64/250/250' }
]
});
});
Multiple selection:
Expressive.Autocomplete.init(document.querySelector('#autocomplete-multi'), {
minLength: 0,
isMultiSelect: true,
data: [
{ id: 12, text: 'Apple' },
{ id: 13, text: 'Microsoft' },
{ id: 42, text: 'Google', image: 'https://picsum.photos/id/64/250/250' }
]
});
A disabled entry stays in the results so people can see it exists.
A click, Enter and selectOption() all leave it as it is,
so no change or expressive:autocomplete fires.
Up and Down skip it, and the announced result count leaves it out,
so a list of only disabled matches announces i18n.noResults.
The selected option and setMenuItems(items, selected)
can still select one, which shows a choice the person cannot remove:
Expressive.Autocomplete.init(document.querySelector('#autocomplete-disabled'), {
minLength: 0,
isMultiSelect: true,
selected: ['vienna'],
data: [
{ id: 'lisbon', text: 'Lisbon' },
{ id: 'oslo', text: 'Oslo', disabled: true },
{ id: 'rome', text: 'Rome' },
{ id: 'vienna', text: 'Vienna', disabled: true }
]
});
Give entries a group to list them under labelled headings. Entries with the same group gather where the group first appears in the results, so sort them yourself if the order matters; entries without one stay outside every group. Up and Down pass over a heading to the next entry, the announced result count leaves headings out, and a click on a heading chooses nothing. The heading is plain text, even with allowUnsafeHTML:
Expressive.Autocomplete.init(document.querySelector('#autocomplete-groups'), {
minLength: 0,
data: [
{ id: 'lisbon', text: 'Lisbon', group: 'Portugal' },
{ id: 'porto', text: 'Porto', group: 'Portugal' },
{ id: 'bergen', text: 'Bergen', group: 'Norway' },
{ id: 'oslo', text: 'Oslo', group: 'Norway' }
]
});
A custom onSearch can load data asynchronously. When the list is
ready, call setMenuItems. The default filter looks at
id and text only:
onSearch: function(text, autocomplete) {
const normSearch = text.toLocaleLowerCase();
autocomplete.setMenuItems(
autocomplete.options.data.filter(function(option) {
return option.id.toString().toLocaleLowerCase().includes(normSearch)
|| (option.text && option.text.toLocaleLowerCase().includes(normSearch));
})
);
}
The third argument is an AbortSignal. It aborts when the
search text changes or the autocomplete is destroyed. Pass it to
fetch() so a slow response for old text never replaces
the results for the current text. Clear the list on any other
error. Set searchDelay so the request waits for typing
to pause instead of going out on every key:
searchDelay: 300,
onSearch: function(text, autocomplete, signal) {
fetch('/api/search?q=' + encodeURIComponent(text), { signal })
.then(function(response) { return response.json(); })
.then(function(items) { autocomplete.setMenuItems(items); })
.catch(function(error) {
// A newer search aborted this one; its results are stale.
if (error.name !== 'AbortError') autocomplete.setMenuItems([]);
});
}
Set allowCreate to offer the typed text as a new value.
The list then ends in an "Add" row whenever the trimmed text matches
no listed suggestion. The comparison ignores case and checks each
suggestion's text, or its id when it has no
text. Choosing the row with Enter or a click calls
onCreate with the trimmed text and fires
expressive:create with { text }.
The selection does not change, so add and select the entry yourself:
allowCreate: true,
onCreate: function(text, autocomplete) {
const entry = { id: text, text: text };
autocomplete.options.data.push(entry);
autocomplete.setMenuItems(autocomplete.options.data, [entry.id], false);
}
Options
| Name | Type | Default | Description |
|---|---|---|---|
data |
Array | [] |
Suggestion list. Each item needs an id. The text, image, and description fields are optional. |
isMultiSelect |
Boolean | false |
If true, the user can select several values. onAutocomplete receives an array. |
maxMenuHeight |
String | '300px' |
Max height of the suggestion menu. |
onAutocomplete |
Function | null |
Called after a selection and when a default value is applied. Receives the selected entries. |
onSearch |
Function | filters id and text |
Called with the text, the instance and an AbortSignal when input events change the search text, including paste, replacement and deletion without key events. Load or filter data, then call setMenuItems. The signal aborts when the search text changes or the autocomplete is destroyed. |
allowCreate |
Boolean | false |
If true, text that matches no listed suggestion exactly ends the list in an "Add" row. Choosing it calls onCreate and fires expressive:create. The selection does not change. |
onCreate |
Function | null |
Called with the trimmed text and the instance when the "Add" row is chosen. |
searchDelay |
Number | 0 |
Milliseconds to wait after typing pauses before calling onSearch, so remote data is fetched once per pause. A search on focus runs at once. Blur, destroy() and choosing a single result cancel a pending search. 0 searches on every input. |
minLength |
Number | 1 |
Characters required before suggestions open. 0 shows the list on click or focus. |
menuOptions |
Object | see note |
Options for
Menu.
Defaults include autoFocus: false,
closeOnClick: false, and coverTrigger: false.
|
allowUnsafeHTML |
Boolean | false |
If true, each data entry's text is inserted as HTML. Ids always stay text. Only use sanitized data. |
selected |
Array | [] |
Initial selected ids (strings or numbers). An id without loaded data displays the id. Pass the selected ids to setMenuItems with the loaded data to resolve their labels. |
Methods
All methods are called on the plugin instance. You can get the instance like this:
const instance = Expressive.Autocomplete.getInstance(elem);
.open();
Open the suggestion menu if the input meets minLength.
instance.open();
.close();
Close the suggestion menu.
instance.close();
.selectOption();
Select (or toggle, when multi-select) the entry with this id.
instance.selectOption(42);
.setMenuItems();
Replace the visible suggestions. Optionally pass selected ids and whether to open the menu (default true).
instance.setMenuItems([
{ id: 'Test' },
{ id: 12, text: 'Apple' },
{ id: 13, text: 'Microsoft' },
{ id: 42, text: 'Google', image: 'https://picsum.photos/id/64/250/250' }
]);
.destroy();
Destroy the plugin instance, remove the menu, and detach its event handlers.
instance.destroy();
Properties
| Name | Type | Description |
|---|---|---|
el |
Element | The input the plugin was initialized with. |
options |
Object | The options the instance was initialized with. |
isOpen |
Boolean | Whether the suggestion menu is open. |
count |
Number | Number of matching options (reset on each input or focus). |
activeIndex |
Number | Index of the keyboard-highlighted option, or -1. |
menu |
Menu | The Menu instance for this autocomplete. |
selectedValues |
Array | The currently selected entries. |