The HTML aria-autocomplete attribute
The aria-autocomplete value tells assistive technology how your code's suggestions appear, as inline, list or both. HTML's autocomplete attribute is a separate feature that handles autofill.
Overview
The aria-autocomplete attribute describes how a combobox, searchbox or textbox presents predictions of what the user is typing. It doesn't provide the functionality, so typing in an input that has it adds no elements and opens no list.
With inline, text completing the input may appear after the caret. With list, a separate element may show possible values. With both, that list can appear with one value selected automatically, and the rest of that value's text after the caret. The default, none, shows no automatic suggestion.
The list and both values come with rules. The element must have aria-controls pointing at the suggestion container and an aria-haspopup value matching the container's role, which a combobox already has for a listbox. Use aria-expanded to show whether the list is displayed.
Inline suggestions have a rule of their own. The suggested part should be presented as selected text, so assistive technology can tell it from what was typed and the user can delete it or replace it by typing on. The suggestion becomes the value if focus leaves the field.
HTML has its own list model. A <datalist> linked from a text-style <input> by its list attribute offers suggestions with no script, and that input has the combobox role. Try it before building a custom widget.
Syntax
<label for="tag">Tag</label>
<input id="tag" role="combobox" aria-autocomplete="list"
aria-expanded="true" aria-controls="tag-list">
<ul id="tag-list" role="listbox" aria-label="Tags">
<li role="option" aria-selected="false">Zebra</li>
<li role="option" aria-selected="false">Zoom</li>
</ul>
Values
| Value |
|---|
| none | inline | list | both |
Example
<label for="city">Destination</label>
<input id="city" role="combobox" autocomplete="off" aria-autocomplete="list" aria-expanded="false" aria-controls="cities">
<ul id="cities" role="listbox" aria-label="Cities" hidden style="list-style:none;margin:4px 0;padding:0;border:1px solid #767676;max-width:220px">
<li id="c1" role="option" aria-selected="false" hidden style="padding:2px 6px">Panama City</li>
<li id="c2" role="option" aria-selected="false" hidden style="padding:2px 6px">Paris</li>
<li id="c3" role="option" aria-selected="false" hidden style="padding:2px 6px">Parma</li>
<li id="c4" role="option" aria-selected="false" hidden style="padding:2px 6px">Porto</li>
<li id="c5" role="option" aria-selected="false" hidden style="padding:2px 6px">Lisbon</li>
</ul>
<p id="out" role="status" style="overflow-wrap:anywhere"></p>
<p>Type pa, then press the arrow keys and Enter, or click a city.</p>
<script>
const input = document.getElementById('city');
const list = document.getElementById('cities');
const out = document.getElementById('out');
const options = [...list.children];
let active = -1;
const shown = () => options.filter(o => !o.hidden);
function highlight() {
options.forEach(o => { o.setAttribute('aria-selected', 'false'); o.style.background = ''; });
const o = shown()[active];
if (o) {
o.setAttribute('aria-selected', 'true');
o.style.background = '#dbeafe';
input.setAttribute('aria-activedescendant', o.id);
} else {
input.removeAttribute('aria-activedescendant');
}
}
function close() {
list.hidden = true;
input.setAttribute('aria-expanded', 'false');
active = -1;
highlight();
}
function pick(o) {
input.value = o.textContent;
close();
out.textContent = 'Chosen: ' + o.textContent + '.';
}
function update() {
const typed = input.value.trim().toLowerCase();
options.forEach(o => { o.hidden = !typed || !o.textContent.toLowerCase().startsWith(typed); });
active = -1;
highlight();
const count = shown().length;
list.hidden = count === 0;
input.setAttribute('aria-expanded', String(count > 0));
out.textContent = typed ? 'The script found ' + count + (count === 1 ? ' match.' : ' matches.') : '';
}
input.addEventListener('input', update);
input.addEventListener('blur', close);
input.addEventListener('keydown', function (e) {
if (list.hidden) {
if (e.key === 'ArrowDown') { e.preventDefault(); update(); }
return;
}
const count = shown().length;
if (e.key === 'ArrowDown') { e.preventDefault(); active = (active + 1) % count; highlight(); }
if (e.key === 'ArrowUp') { e.preventDefault(); active = active > 0 ? active - 1 : count - 1; highlight(); }
if (e.key === 'Escape') close();
if (e.key === 'Enter' && active >= 0) { e.preventDefault(); pick(shown()[active]); }
});
list.addEventListener('mousedown', function (e) {
e.preventDefault();
if (e.target.getAttribute('role') === 'option') pick(e.target);
});
</script>
Best practices
- An editable combobox that autocompletes must carry
aria-autocomplete, set to the value that matches what it does. - Don't change the value to signal a suggestion. The property isn't meant to show that one exists.
- When a suggestion is selected automatically as the user types, keep DOM focus on the input and point aria-activedescendant at that suggestion. That model requires both.
- Skip it on select-only comboboxes, where no text input is possible.























