Skip to content

ARIA Properties and States — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about ARIA Properties and States. We cover key concepts, practical examples, and best practices to help you master this topic.

ARIA properties and states convey dynamic information about UI components to assistive technologies, such as whether a dropdown is expanded, a checkbox is checked, or an error is present.

What You'll Learn

  • The difference between ARIA properties and states
  • Commonly used properties like aria-label, aria-describedby, aria-controls
  • Commonly used states like aria-expanded, aria-checked, aria-selected
  • How to dynamically update ARIA attributes
  • Relationship attributes and live region properties

Why It Matters

  • Properties and states provide the dynamic context that makes widgets usable
  • Missing or incorrect ARIA attributes break screen reader announcements
  • Dynamic updates require JavaScript to keep ARIA in sync with visual state
  • WCAG requires proper state announcements for custom widgets

Real-World Use

  • A dropdown menu uses aria-expanded to indicate open/closed state
  • A form field with an error uses aria-invalid and aria-describedby
  • A custom checkbox uses aria-checked and handles mixed states
  • A tab panel uses aria-controls to link tabs to their panels
flowchart LR
  A[ARIA Properties] --> B[Static Information]
  A --> C[Relationships]
  D[ARIA States] --> E[Dynamic Information]
  D --> F[User Interaction]
  A --> G[label, describedby, controls]
  D --> H[expanded, checked, selected]

Properties vs States

The distinction between properties and states is subtle but important.

Properties are attributes that are less likely to change once set. They describe characteristics of an element:

  • aria-label — provides an accessible name
  • aria-describedby — provides an accessible description
  • aria-controls — indicates which element this element controls
  • aria-owns — indicates a parent-child relationship not reflected in the DOM
  • aria-flowto — suggests an alternative reading order

States are attributes that change frequently in response to user interaction or system events:

  • aria-expanded — whether a collapsible element is expanded
  • aria-checked — whether a checkbox or switch is checked
  • aria-selected — whether an option is selected
  • aria-pressed — whether a toggle button is pressed
  • aria-current — the current item in a collection (page, step, location)
  • aria-hidden — whether the element is hidden from assistive technologies

Code Example: Form Input with Error States

<form onsubmit="return validateForm(event)">
    <div style="margin-bottom:1rem;">
        <label for="email">Email Address</label>
        <input type="email"
               id="email"
               name="email"
               required
               aria-required="true"
               aria-invalid="false"
               aria-describedby="email-hint email-error">
        <div id="email-hint" style="font-size:0.85rem; color:#666;">
            Enter your work email address.
        </div>
        <div id="email-error"
             role="alert"
             style="color:#cc0000; font-size:0.85rem;"
             hidden>
            Please enter a valid email address.
        </div>
    </div>
    <button type="submit">Subscribe</button>
</form>

<script>
function validateForm(event) {
    event.preventDefault();
    const email = document.getElementById('email');
    const error = document.getElementById('email-error');

    if (!email.value.includes('@') || !email.value.includes('.')) {
        email.setAttribute('aria-invalid', 'true');
        error.hidden = false;
        error.textContent = 'Please enter a valid email address.';
        email.focus();
        return false;
    }

    email.setAttribute('aria-invalid', 'false');
    error.hidden = true;
    alert('Subscribed successfully!');
    return false;
}
</script>

Expected output: Screen readers announce "Email Address, edit, required, enter your work email address". When validation fails, the announcement changes to "Email Address, edit, invalid, please enter a valid email address."

Code Example: Menu with Expanded State

<nav aria-label="Account menu">
    <button aria-haspopup="true"
            aria-expanded="false"
            aria-controls="account-menu"
            id="account-btn"
            onclick="toggleMenu()">
        My Account
    </button>

    <ul id="account-menu"
        role="menu"
        aria-labelledby="account-btn"
        hidden
        style="list-style:none; border:1px solid #ddd; padding:0.5rem; background:white;">
        <li role="menuitem" tabindex="-1">Profile</li>
        <li role="menuitem" tabindex="-1">Orders</li>
        <li role="menuitem" tabindex="-1">Settings</li>
        <li role="menuitem" tabindex="-1">Sign Out</li>
    </ul>
</nav>

<script>
function toggleMenu() {
    const button = document.getElementById('account-btn');
    const menu = document.getElementById('account-menu');
    const isExpanded = button.getAttribute('aria-expanded') === 'true';

    button.setAttribute('aria-expanded', !isExpanded);
    menu.hidden = isExpanded;

    if (!isExpanded) {
        menu.querySelector('[role="menuitem"]').focus();
    } else {
        button.focus();
    }
}
</script>

Expected output: Screen reader announces "My Account, menu popup, collapsed". Clicking changes it to "expanded". The aria-controls attribute links the button to the menu. Focus moves to the first menu item when opened.

Code Example: Progress Bar with Value Properties

<div id="progress-container">
    <div role="progressbar"
         id="upload-progress"
         aria-valuenow="0"
         aria-valuemin="0"
         aria-valuemax="100"
         aria-label="File upload progress"
         style="width:300px; height:20px; background:#eee; border-radius:10px; overflow:hidden;">
        <div id="progress-bar"
             style="width:0%; height:100%; background:#4CAF50; transition:width 0.3s;"></div>
    </div>
    <span id="progress-text" style="margin-left:0.5rem;">0%</span>
</div>

<button onclick="startUpload()">Upload File</button>

<script>
function startUpload() {
    const progress = document.getElementById('upload-progress');
    const bar = document.getElementById('progress-bar');
    const text = document.getElementById('progress-text');
    let value = 0;

    const interval = setInterval(() => {
        value += 5;
        progress.setAttribute('aria-valuenow', value);
        bar.style.width = value + '%';
        text.textContent = value + '%';

        if (value >= 100) {
            clearInterval(interval);
            progress.setAttribute('aria-valuenow', '100');
            text.textContent = 'Complete!';
        }
    }, 200);
}
</script>

Expected output: As the upload progresses, screen readers announce "File upload progress, 50 percent" and continue updating. The aria-valuenow attribute ensures the current value is always communicated.

Common Mistakes

  1. Setting ARIA attributes once and never updating — If aria-expanded stays "false" after a menu opens, the screen reader announces incorrect information.
  2. Using aria-label to repeat visible text — If a button already says "Close" visually, aria-label="Close" is redundant. Use aria-label only when the visible text is insufficient.
  3. Confusing aria-labelledby with aria-describedby — aria-labelledby provides the element's accessible name (replacing visual text). aria-describedby provides additional description.
  4. Forgetting aria-required on custom form controls — Native form elements have implicit required states. Custom controls need aria-required="true".
  5. Using aria-hidden inconsistently — If an element is visually hidden but aria-hidden="false", screen readers announce content the user cannot see.
  6. Not testing state announcements — You cannot assume the correct state will be announced. Test with actual screen readers to verify.
  7. Overusing aria-live regions — Every live region increases cognitive load for screen reader users. Use them only for truly dynamic content.

Practice Questions

  1. What is the difference between an ARIA property and an ARIA state? Properties are static attributes that describe characteristics. States change frequently with user interaction or system events.
  2. What does aria-expanded indicate and on which elements should it be used? It indicates whether a collapsible element is expanded or collapsed. Use it on buttons or elements that control the visibility of other content.
  3. What is the relationship between aria-controls and the id of the controlled element? aria-controls references the id attribute of the element being controlled, creating an explicit relationship for screen readers.
  4. When would you use aria-valuenow, aria-valuemin, and aria-valuemax? On progress indicators like progress bars, sliders, and spinners to communicate the current value and range.
  5. Challenge: Build an accessible custom slider (range input) using role="slider" with aria-valuenow, aria-valuemin, aria-valuemax, and aria-orientation. Implement keyboard support (Arrow keys increment/decrement, Home/End for min/max) and test with a screen reader.

FAQ

Can I use aria-label on any HTML element?

Yes, aria-label can be used on any element. However, it only affects the accessibility tree when the element has a role that supports naming, such as interactive elements, landmark regions, and widgets.

What is the difference between aria-hidden and the HTML hidden attribute?

aria-hidden removes an element from the accessibility tree but keeps it visually rendered. The hidden attribute hides it both visually and from the accessibility tree.

{{< faq "How do I handle mixed checkbox states (partially checked)?" "Use aria-checked=\"mixed\" for tri-state checkboxes that represent a group selection where some items are selected and others are not." >}}
Does aria-describedby work across shadow DOM boundaries?

No. ARIA ID references (aria-labelledby, aria-describedby, aria-controls) do not cross shadow DOM boundaries. The referenced element must be in the same DOM tree.

Can I dynamically change ARIA attributes with JavaScript?

Yes. ARIA attributes are HTML attributes and can be set with setAttribute. Screen readers detect these changes and announce them when the attributes affect live regions or states.

Mini Project

Build an accessible custom color picker widget. Use role="radiogroup" for the color options, role="radio" for each color swatch with aria-checked for the selected state, aria-label to announce the color name, and a live region to announce the selected color. Include a text input that updates when a color is selected (two-way binding). Ensure keyboard navigation with Arrow keys. Test with a screen reader.

What's Next

Continue with Lesson 9: Semantic HTML to learn how proper HTML structure creates Accessibility without ARIA.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro