aria-activedescendant
On a composite widget, points to the ID of the descendant that is currently active. DOM focus stays on the container while a virtual focus moves across children. Alternative to roving tabindex for listboxes, comboboxes, and grids.
When to use
On a composite widget where you want a single tab stop but the user still needs to move a highlight across many items — combobox input with a listbox popup, single-tab-stop listbox, treegrid, datagrid. DOM focus stays on the container element (the input, the listbox, the grid). As the user presses arrow keys, you update aria-activedescendant to the ID of the newly highlighted child, and AT announces it as if focus had moved there.
The alternative pattern is roving tabindex: actually move DOM focus and adjust tabindex values. Pick aria-activedescendant when keeping browser focus on the container simplifies your code — typically because the container is also where the user types or where keyboard shortcuts attach.
How it behaves
The value is a single element ID — never a list. The referenced element must be a descendant of the container that has the aria-activedescendant attribute (or owned via aria-owns). When the value changes, the accessibility-tree position of the “virtual focus” moves to that element and AT announces its label, role, and state.
Two things you still own:
- Visual styling. AT knows where the virtual focus is, but the browser does not paint a focus ring on the descendant. Add your own highlight CSS, often via a class toggled in sync.
- Scrolling. If the active descendant is below the fold, you must scroll it into view; the browser will not.
When the popup closes or the widget loses focus, clear aria-activedescendant to an empty string or remove the attribute.
Common failures
- Pointing at an ID that is not a descendant (and is not owned via
aria-owns). The AT ignores the reference. - Mixing patterns: applying both
tabindex="0"to the descendant and settingaria-activedescendanton the container. Pick one focus strategy; doing both fights itself. - Forgetting to update the visual highlight when the attribute changes — sighted users see no movement while AT users hear the move.
- Leaving a stale ID after the option is removed from the list (filtered combobox results, for instance).
- Updating
aria-activedescendanton mouse hover. The attribute should track the keyboard-driven active item only. - Setting it on an element that does not contain the descendant in the AT tree (forgot
aria-ownsfor a portalled list).
Example
<label for="fruit">Fruit</label>
<input
id="fruit"
role="combobox"
type="text"
aria-controls="fruit-listbox"
aria-expanded="true"
aria-activedescendant="fruit-2"
autocomplete="off"
>
<ul id="fruit-listbox" role="listbox">
<li id="fruit-1" role="option">Apple</li>
<li id="fruit-2" role="option" class="is-active">Banana</li>
<li id="fruit-3" role="option">Cherry</li>
</ul>