Editable combobox with
list autocomplete
(manual selection), illustrated with an AD&D 2nd edition
bestiary —
managed, async fetch, and
external rich results.
mode: 'managed' (default) — synchronous
search over a local list, with an optional open button
(tabindex="-1").
const combobox = new Combobox($input, $listbox, {
button,
render: (result, props) =>
`<li class="list-group-item"${props}>${result}</li>`,
search: (input, { signal }) => {
if (input.length < 1) return monsters;
return monsters.filter(name =>
name.toLowerCase().startsWith(input.toLowerCase()),
);
},
});
combobox.init();
Async search with fetch,
AbortSignal, and event log
(loading / loaded / submit /
update).
Idle
const combobox = new Combobox($input, $listbox, {
render: (result, props) =>
`<li class="list-group-item"${props}>${result}</li>`,
search: async (input, { signal }) => {
const response = await fetch(monstersUrl, { signal });
const monsters = await response.json();
if (input.length < 1) return monsters;
return monsters.filter(name =>
name.toLowerCase().startsWith(input.toLowerCase()),
);
},
});
$input.addEventListener('combobox:submit', ({ detail }) => {
console.log(detail.value, detail.option);
});
mode: 'external' +
selectMode: 'custom' — grouped HTML, debounce,
minLength, openOnEmpty, and
combobox:empty. Enter on a focused option follows the
link; Enter without focus would submit a parent form.
Idle — try “be”, “mi”, or “xx”.
const combobox = new Combobox($input, $listbox, {
mode: 'external',
selectMode: 'custom',
debounce: 280,
minLength: 2,
openOnEmpty: true,
search: async (query, { signal }) => {
const html = await renderGroupedResults(query, signal);
return { html };
},
onSelect: ({ option }) => {
option?.element.querySelector('a')?.click();
},
});
$input.addEventListener('combobox:empty', ({ detail }) => {
showNoResults(detail.value);
});
| Key | Action |
|---|---|
| ↓ | Open list and move to first option |
| Alt + ↓ / ↑ | Open list without selecting |
| ↑ | Open list and move to last option |
| Enter | Accept focused option; without focus, close and allow form submit |
| Tab | Accept focused option, otherwise close |
| Escape | Close list, or clear field if already closed |
role="combobox" +
aria-autocomplete="list" on the input
aria-controls / aria-expanded on input
(and button)
id; options use
aria-selected when visually focused
aria-activedescendant on the input while navigating
(attribute removed when inactive)
aria-busy on the listbox while
search is in flight
external mode, group headers are not options —
only [role="option"] participate; each option needs
a stable id