aria-busy
Marks a region as in the middle of an update so assistive technology suppresses interim announcements. Set to "true" while loading or while large DOM changes are in progress; flip back to "false" when the region is stable.
When to use
When a region is being asynchronously rebuilt — a search result list streaming in, a table re-sorting itself, a chat panel waiting on a network response. Without aria-busy, screen readers connected to a live region may announce every partial state as it appears, which is noisy and often confusing.
The pattern is:
- Set
aria-busy="true"on the container before you start mutating it. - Do the work (clear children, fetch data, re-render).
- Set
aria-busy="false"once the DOM is stable.
If the container is also a live region (aria-live="polite" or aria-live="assertive"), the screen reader holds back announcements while aria-busy="true" and produces a single coherent announcement after aria-busy flips back.
Outside a live region the attribute is still useful: it signals to AT that the user should not act on the content yet, and it interacts well with progress indicators.
How to keep it in sync
Valid values are "true" and "false". Default to "false" (or omit).
The attribute must wrap the mutation. The common bug is setting aria-busy="true", mutating the DOM, and forgetting to set it back — the region then appears permanently “loading” to assistive technology even though everything is visually fine.
Pair it with a visible loading indicator (spinner, skeleton, progress text) so sighted users get the same signal. The aria-busy attribute is for AT only; it does not render anything on its own.
Common failures
- Setting
aria-busy="true"and never setting it back to"false". - Setting
aria-busyafter the mutation has already happened — too late, the interim announcements have already fired. - Using
aria-busyalone with noaria-liveand no visible spinner. Sighted users see nothing; AT users hear nothing. - Setting
aria-busyon the whole<body>for the entire app lifetime. The scope should match the region that is actually changing. - Using
aria-busyinstead ofrole="progressbar"for a determinate progress indicator. The two are complementary, not alternatives. - Forgetting to also disable interactive controls inside the busy region — sighted users still see and can click them.
Example
<section
id="results"
aria-live="polite"
aria-busy="true"
>
<p>Loading results…</p>
</section>
<script>
fetch('/api/search')
.then((r) => r.json())
.then((data) => {
const region = document.getElementById('results');
region.innerHTML = renderResults(data);
region.setAttribute('aria-busy', 'false');
});
</script>