The HTML aria-busy attribute
Set aria-busy="true" on an element before you change it, and set it back to false after the last change. Assistive technology may then wait and treat those changes as a single update. The attribute itself doesn't hide or disable anything.
Overview
The aria-busy state tells assistive technology that an element is being modified. It takes true or false, and false is the default for all elements. It counts as a state, meaning a value that may change in response to user action or automated processes.
WAI-ARIA 1.2 words the waiting as an option. Assistive technologies may want to wait, and while the value is true they may ignore changes to content the element owns, then process those changes as a single atomic update when it turns false. The 1.3 Working Draft softens those verbs to could and can.
WAI-ARIA 1.2 says authors must mark a widget busy when script or loading leaves it missing required owned elements, such as a listbox with no options yet, and the 1.3 draft only says they may. Both versions say authors should set it on a feed while articles are added or removed at either end.
A busy region's children stay in the accessibility tree, and a button inside one still takes focus and fires its click. Only the element itself takes the busy state.
Syntax
<div id="orders" aria-live="polite" aria-busy="true">
<!-- rows being replaced -->
</div>
<script>
document.getElementById('orders').ariaBusy = 'false';
</script>
Values
| Value |
|---|
| true | false |
Example
<p style="margin:0 0 8px">Press Refresh. The rows change one at a time while aria-busy stays true.</p>
<button id="refresh" type="button">Refresh stock</button>
<ul id="stock" aria-live="polite" aria-busy="false" aria-describedby="loaded" style="margin:8px 0">
<li>Lisbon: <span>12</span> units</li>
<li>Oslo: <span>30</span> units</li>
<li>Paris: <span>7</span> units</li>
</ul>
<progress id="loaded" max="3" value="0" aria-label="Warehouses updated"></progress>
<p id="state" role="status" style="margin:8px 0 0">aria-busy is false.</p>
<script>
const stock = document.getElementById('stock');
const loaded = document.getElementById('loaded');
const state = document.getElementById('state');
const counts = stock.querySelectorAll('span');
const wait = ms => new Promise(done => setTimeout(done, ms));
document.getElementById('refresh').addEventListener('click', async () => {
if (stock.getAttribute('aria-busy') === 'true') return;
stock.setAttribute('aria-busy', 'true');
loaded.value = 0;
try {
for (let i = 0; i < counts.length; i++) {
state.textContent = 'aria-busy is true. Updating row ' + (i + 1) + ' of 3.';
await wait(500);
counts[i].textContent = Number(counts[i].textContent) + 1;
loaded.value = i + 1;
}
} finally {
stock.setAttribute('aria-busy', 'false');
state.textContent = 'aria-busy is false. All 3 rows are updated.';
}
});
</script>
Best practices
- Clear
aria-busyin afinallyblock, so a failed request can't leave the element marked busy. - Put a loading message outside the busy element, since changes inside it may be ignored until the value returns to false.
- Link a loading region to its <progress> bar with aria-describedby. Keep
aria-busy="true"on the region until it finishes loading. - Drive a loading style from the same attribute with the selector
[aria-busy="true"]. The selector follows script changes to the attribute, so one line of script updates the state and the style.























