Skip to main content
    Back to blog
    DevelopmentPublished on October 21, 2026

    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

    ARIA and Dynamic Content: When Attributes Help and When They Hurt

    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

    1. Web Accessibility: Who It Really Serves and Where to Start
    2. Semantic HTML and Alt Text: The Foundation of an Accessible Website
    3. Accessible Forms: Labels, Hints, Errors and Autofill
    4. Keyboard and Focus: Building a Website You Can Use Without a Mouse
    5. Contrast, Motion and Button Size: Visual Accessibility
    6. ARIA and Dynamic Content: When Attributes Help and When They Hurt — you are here
    7. Accessibility in React: IDs, Focus on Route Change and Announcements
    8. 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

    Contact

    Let’s talk about your project

    Leave your details and we’ll get back to you within 2 business hours for a free consultation. Tell us what you’re building - we’ll help you choose the right approach.

    Business hours

    Mon–Fri: 08:00 AM – 5:00 PM (PST)
    Sat–Sun: Closed

    Request a call

    Fill out the form and we’ll reach out shortly

    By clicking the button, you agree to our Privacy Policy

    We value your privacy

    We use analytics cookies, including third-party ones, to see how the site is used and improve it