ARIA and Dynamic Content: When Attributes Help and When They Hurt
The first rule of ARIA, common mistakes, element states, and live regions for notifications, loading and single-page apps

Part 6 of 8 in the "Accessible Web in Practice" series. Previous part: Contrast, Motion and Button Size: Visual Accessibility.
Websites use more and more ARIA attributes, and the errors grow with them. This part covers when ARIA is genuinely needed, how not to break it, and how to tell a screen reader about changes on the page.
ARIA: when it helps and when it hurts
WAI-ARIA is a set of attributes (role, aria-*) that change how an element is represented in the accessibility tree. ARIA changes nothing about behavior: role="button" won't make a div focusable or add keyboard handling.
The first rule of ARIA
If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so. — W3C, Using ARIA
This rule has numbers behind it. In the WebAIM Million 2026, pages with ARIA averaged 59.1 errors, pages without ARIA averaged 42, and the average number of ARIA attributes per page passed 133, up 27% in a year. That doesn't mean ARIA causes errors by itself: it's more common on complex pages. But wrong ARIA is worse than none — a wrong role or a stale state actively misinforms the user.
Common mistakes:
<!-- ❌ aria-label on an element with no role is ignored or read unpredictably -->
<div aria-label="Product card">...</div>
<!-- ❌ Redundant: the screen reader already says "button" -->
<button role="button">OK</button>
<!-- ❌ aria-hidden on a focusable element: focus lands "nowhere" -->
<a href="/cart" aria-hidden="true">Cart</a>
<!-- ❌ aria-label doesn't match the visible text — breaks voice control -->
<button aria-label="submit">Send request</button>
The last example violates criterion 2.5.3 Label in Name: the accessible name must contain the visible text, ideally starting with it.
Where ARIA is genuinely needed
ARIA is indispensable when HTML has no suitable element or when you need to convey state:
Disclosure (show/hide)
<button type="button" aria-expanded="false" aria-controls="faq-1">
How much does a website cost?
</button>
<div id="faq-1" hidden>
<p>A landing page starts at ... A multi-page site starts at ...</p>
</div>
document.querySelectorAll('[aria-controls]').forEach((button) => {
const panel = document.getElementById(button.getAttribute('aria-controls'));
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
});
For simple cases there's a native, JavaScript-free option — <details> and <summary>:
<details>
<summary>How much does a website cost?</summary>
<p>A landing page starts at ... A multi-page site starts at ...</p>
</details>
Toggle button
<button type="button" aria-pressed="false">Favorites only</button>
Labels and descriptions
aria-labelledby— the name comes from another visible element (a dialog title, a section heading);aria-describedby— additional description (a hint, an error);aria-label— a name when there's no visible text (an icon button).
Priority: visible text inside the element → aria-labelledby → aria-label. Names users can see are always better than invisible ones.
States: aria-expanded, aria-pressed, aria-selected, aria-checked, aria-current, aria-invalid, aria-busy, aria-disabled. The key is keeping them in sync with the visual state. A menu that's open while aria-expanded="false" is worse than no attribute at all.
For complex widgets (tabs, combobox, menu, tree, grid), see the ARIA Authoring Practices Guide. But heed the authors' own warning: APG patterns are demonstrations, not production-ready components, and assistive technology support has to be verified separately.
Dynamic content and announcements
In single-page apps, content changes without a reload: an item gets added to the cart, a notification appears, search results load. Sighted users see it; a screen reader stays silent unless you tell it.
Live regions
The visually-hidden class in the examples below hides an element visually but keeps it available to screen readers. Its CSS is in Semantic HTML and Alt Text.
<!-- The region must exist in the DOM BEFORE its text changes -->
<div id="status" role="status" aria-live="polite" class="visually-hidden"></div>
function announce(message) {
const region = document.getElementById('status');
region.textContent = ''; // reset so a repeated message is read again
setTimeout(() => { region.textContent = message; }, 100);
}
addToCartButton.addEventListener('click', async () => {
await addToCart(productId);
announce('Added to cart. Your cart has 3 items.');
});
| Attribute | Behavior | When |
|---|---|---|
aria-live="polite" / role="status" |
Read once the screen reader finishes its current phrase | Status updates, notifications, "12 results found" |
aria-live="assertive" / role="alert" |
Interrupts and reads immediately | Urgent only: an error, a session about to expire |
The most common mistake is creating the region together with the message (for example, rendering a toast with role="alert" at the moment of the event). Many screen readers only track changes in regions that were already on the page. Keep one or two empty regions in the markup from the start and change their text.
Don't overuse assertive: constant interruptions are as annoying as pop-ups.
Loading
<section aria-labelledby="results-title" aria-busy="true">
<h2 id="results-title">Search results</h2>
<!-- skeleton -->
</section>
aria-busy="true" signals that content is updating. Once loading finishes, remove it and announce the result through a live region: "12 jobs found."
Toasts and notifications
If a notification disappears on its own, people need enough time to read it (criterion 2.2.1), and if it contains an action ("Undo"), they need to reach it with a keyboard. A reliable rule: notifications with actions don't auto-dismiss, and important actions are also available outside the toast.
Infinite scroll
An infinite feed makes the footer unreachable by keyboard and is hard to use with a screen reader. A "Load more" button solves both problems and gives users control. After it's pressed, move focus to the first new item.
The "Accessible Web in Practice" series
- Web Accessibility: Who It Really Serves and Where to Start
- Semantic HTML and Alt Text: The Foundation of an Accessible Website
- Accessible Forms: Labels, Hints, Errors and Autofill
- Keyboard and Focus: Building a Website You Can Use Without a Mouse
- Contrast, Motion and Button Size: Visual Accessibility
- ARIA and Dynamic Content: When Attributes Help and When They Hurt — you are here
- Accessibility in React: IDs, Focus on Route Change and Announcements
- How to Test Accessibility: Automation, Manual Checks and a Checklist
Next part: Accessibility in React: IDs, Focus on Route Change and Announcements
Agama Labs builds websites and web applications. Want to check how accessible your site is? Write to us: hello@agamalabs.com


