ARIA Implementation

Enhancing accessibility and agent understanding with ARIA attributes

ARIA (Accessible Rich Internet Applications) provides additional semantic information when HTML alone isn't sufficient. While semantic HTML should always be your first choice, ARIA attributes fill critical gaps for complex interactive widgets, dynamic content, and custom controls.

For BiModal Design, ARIA serves a dual purpose: it helps screen reader users navigate your site and provides explicit semantics that AI agents can parse to understand interactive elements and application state.

The First Rule of ARIA

"No ARIA is better than bad ARIA."

The official ARIA authoring practices begin with this principle: Don't use ARIA unless you have to. If you can achieve the same result with semantic HTML, use HTML. ARIA should enhance, not replace, proper HTML structure.

The Five Rules of ARIA

  1. 1. If you can use a native HTML element or attribute, use it instead of ARIA
  2. 2. Don't change native semantics unless you really have to
  3. 3. All interactive ARIA controls must be usable with the keyboard
  4. 4. Don't use role="presentation" or aria-hidden on focusable elements
  5. 5. All interactive elements must have an accessible name

ARIA Categories

ARIA attributes fall into three main categories:

Roles

Define what an element is or does

role="button"
role="navigation"
role="dialog"

Properties

Give extra meaning or semantics

aria-label="..."
aria-describedby="..."
aria-required="true"

States

Indicate current condition of an element

aria-expanded="true"
aria-selected="true"
aria-disabled="true"

When to Use ARIA

✓ Use ARIA When:

  • • Building custom interactive widgets not available in HTML (tabs, accordions)
  • • Adding labels to elements without visible text
  • • Indicating dynamic state changes (expanded, selected)
  • • Creating live regions that announce updates
  • • Describing relationships between elements
  • • Providing additional context for agents and screen readers

✗ Don't Use ARIA When:

  • • A native HTML element does the job (use <button>, not <div role="button">)
  • • You're trying to "fix" bad HTML structure (fix the HTML instead)
  • • You don't understand what the attribute does (research first)
  • • It's redundant (e.g., <button role="button">)

Essential ARIA Attributes

aria-label

Provides an accessible name when no visible text label exists. Essential for icon buttons and elements without text content.

❌ Bad

<button>
  <svg>...</svg>
</button>
<!-- Agents/screen readers hear:
     "button" -->

✅ Good

<button aria-label="Close dialog">
  <svg>...</svg>
</button>
<!-- Agents/screen readers hear:
     "Close dialog, button" -->

💡 Pro Tip: Use aria-label when you need to provide a different label than the visible text, or when there's no visible text at all.

aria-labelledby

References another element's ID to use its text as the label. Useful for associating headings with regions or combining multiple text sources.

<section aria-labelledby="products-heading">
  <h2 id="products-heading">Our Products</h2>
  <!-- Section is labeled "Our Products" -->
</section>

<dialog aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirm Purchase</h2>
  <p>Are you sure you want to buy this item?</p>
  <button>Confirm</button>
</dialog>

aria-describedby

References an element that describes or provides additional context. Perfect for form field hints and error messages.

<label for="password">Password</label>
<input
  type="password"
  id="password"
  aria-describedby="password-hint password-error"
/>
<p id="password-hint">Must be at least 8 characters</p>
<p id="password-error" role="alert">Password is too short</p>

aria-expanded

Indicates whether a collapsible element is expanded or collapsed. Critical for dropdowns, accordions, and disclosure widgets.

<!-- Collapsed state -->
<button
  aria-expanded="false"
  aria-controls="dropdown-menu"
>
  Menu
</button>
<div id="dropdown-menu" hidden>
  <a href="/option1">Option 1</a>
  <a href="/option2">Option 2</a>
</div>

<!-- Expanded state (after click) -->
<button
  aria-expanded="true"
  aria-controls="dropdown-menu"
>
  Menu
</button>
<div id="dropdown-menu">
  <a href="/option1">Option 1</a>
  <a href="/option2">Option 2</a>
</div>

aria-current

Indicates the current item in a set of elements. Essential for navigation to show which page is active.

<nav aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products" aria-current="page">Products</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

<!-- Other valid values: -->
<!-- aria-current="step" (in a multi-step process) -->
<!-- aria-current="location" (in breadcrumbs) -->
<!-- aria-current="date" (in a calendar) -->
<!-- aria-current="time" (in a schedule) -->

aria-hidden

Hides content from assistive technologies and agents while keeping it visually present. Use for purely decorative elements.

<!-- Decorative icon (agents should ignore) -->
<button>
  <svg aria-hidden="true">...</svg>
  <span>Save</span>
</button>

<!-- Icon with meaning (don't hide) -->
<button aria-label="Save">
  <svg>...</svg>
</button>

⚠ Warning: Never use aria-hidden="true" on interactive or focusable elements. This creates a "focus trap" where users can tab to elements they can't see or access.

aria-live

Creates a "live region" that announces dynamic content changes. Essential for status messages, notifications, and real-time updates.

<!-- Polite updates (wait for user to pause) -->
<div aria-live="polite" aria-atomic="true">
  Item added to cart
</div>

<!-- Assertive updates (interrupt immediately) -->
<div aria-live="assertive" role="alert">
  Error: Payment failed
</div>

<!-- Status updates -->
<div role="status" aria-live="polite">
  Saving...
</div>

Live Region Politeness Levels:

  • off: No announcements
  • polite: Announce when user is idle (default)
  • assertive: Interrupt immediately (use sparingly)

Common Interactive Patterns

Accordion

<div>
  <h3>
    <button
      aria-expanded="false"
      aria-controls="section1"
      id="accordion1"
    >
      Section 1 Title
    </button>
  </h3>
  <div
    id="section1"
    role="region"
    aria-labelledby="accordion1"
    hidden
  >
    <p>Section 1 content...</p>
  </div>

  <h3>
    <button
      aria-expanded="true"
      aria-controls="section2"
      id="accordion2"
    >
      Section 2 Title
    </button>
  </h3>
  <div
    id="section2"
    role="region"
    aria-labelledby="accordion2"
  >
    <p>Section 2 content...</p>
  </div>
</div>

Tabs

<div>
  <div role="tablist" aria-label="Content sections">
    <button
      role="tab"
      aria-selected="true"
      aria-controls="panel1"
      id="tab1"
    >
      Tab 1
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel2"
      id="tab2"
      tabindex="-1"
    >
      Tab 2
    </button>
    <button
      role="tab"
      aria-selected="false"
      aria-controls="panel3"
      id="tab3"
      tabindex="-1"
    >
      Tab 3
    </button>
  </div>

  <div
    role="tabpanel"
    id="panel1"
    aria-labelledby="tab1"
  >
    <p>Content for tab 1...</p>
  </div>

  <div
    role="tabpanel"
    id="panel2"
    aria-labelledby="tab2"
    hidden
  >
    <p>Content for tab 2...</p>
  </div>

  <div
    role="tabpanel"
    id="panel3"
    aria-labelledby="tab3"
    hidden
  >
    <p>Content for tab 3...</p>
  </div>
</div>

Modal Dialog

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
  aria-describedby="dialog-desc"
>
  <h2 id="dialog-title">Confirm Action</h2>
  <p id="dialog-desc">Are you sure you want to continue?</p>

  <button>Cancel</button>
  <button>Confirm</button>

  <button aria-label="Close dialog">
    <svg aria-hidden="true">×</svg>
  </button>
</div>

⚠ Important: When a modal opens, focus must move to the dialog and trap keyboard navigation within it until closed. This requires JavaScript focus management.

Dropdown Menu

<nav>
  <button
    aria-expanded="false"
    aria-controls="menu1"
    aria-haspopup="true"
  >
    Products
  </button>

  <ul id="menu1" role="menu" hidden>
    <li role="none">
      <a href="/product1" role="menuitem">Product 1</a>
    </li>
    <li role="none">
      <a href="/product2" role="menuitem">Product 2</a>
    </li>
    <li role="none">
      <a href="/product3" role="menuitem">Product 3</a>
    </li>
  </ul>
</nav>

ARIA for Agents

AI agents use ARIA attributes to understand interactive elements and application state. Here's what agents look for:

Interactive Element Labels

Agents need to know what buttons and links do. aria-label provides this context when visual text isn't sufficient.

<button aria-label="Add to cart">
  <svg>...</svg>
</button>
<!-- Agent understands: "This button adds items to cart" -->

State Information

Agents use aria-expanded, aria-selected, andaria-current to understand UI state without visual cues.

<button aria-expanded="true">Menu</button>
<!-- Agent knows: "This menu is currently open" -->

<a href="/products" aria-current="page">Products</a>
<!-- Agent knows: "User is currently on Products page" -->

Element Relationships

aria-controls, aria-labelledby, andaria-describedby help agents understand how elements relate to each other.

<button aria-controls="results">Search</button>
<div id="results">...</div>
<!-- Agent knows: "This button controls the results section" -->

Form Validation

aria-required, aria-invalid, andaria-describedby help agents understand form requirements and errors.

<input
  type="email"
  aria-required="true"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error">Please enter a valid email</p>
<!-- Agent knows: "This field is required and currently invalid" -->

Common ARIA Mistakes

1. Redundant ARIA

Don't add ARIA that duplicates native HTML semantics.

❌ Wrong

<button role="button">
<nav role="navigation">
<main role="main">

✅ Correct

<button>
<nav>
<main>

2. Breaking Native Semantics

Don't override native HTML roles unless absolutely necessary.

<!-- ❌ Wrong: Breaking button semantics -->
<button role="link">Click here</button>

<!-- ✅ Correct: Use the right element -->
<a href="/page">Click here</a>

3. Hiding Focusable Elements

Never use aria-hidden="true" on interactive elements.

<!-- ❌ Wrong: Creates invisible focus trap -->
<button aria-hidden="true">Submit</button>

<!-- ✅ Correct: Hide with CSS if needed -->
<button style="display: none">Submit</button>

4. Missing Required Attributes

Some ARIA patterns require specific attribute combinations.

<!-- ❌ Wrong: aria-expanded without aria-controls -->
<button aria-expanded="false">Menu</button>

<!-- ✅ Correct: Complete pattern -->
<button aria-expanded="false" aria-controls="menu-items">
  Menu
</button>
<div id="menu-items">...</div>

5. Incorrect State Values

ARIA states must have valid values (usually "true" or "false" as strings).

<!-- ❌ Wrong: Invalid values -->
<button aria-expanded="yes">Menu</button>
<input aria-required="1" />

<!-- ✅ Correct: Use "true" or "false" -->
<button aria-expanded="true">Menu</button>
<input aria-required="true" />

Testing ARIA Implementation

1. Accessibility Tree

Inspect the accessibility tree in Chrome/Firefox DevTools to see how assistive tech interprets your markup.

Chrome DevTools → Elements → Accessibility pane

2. Screen Readers

Test with actual screen readers:

  • • NVDA (Windows, free)
  • • JAWS (Windows, paid)
  • • VoiceOver (macOS/iOS, built-in)

3. axe DevTools

Browser extension that automatically detects ARIA issues and accessibility violations.

Get axe DevTools →

4. WAVE

Web accessibility evaluation tool that highlights ARIA usage and potential issues.

Use WAVE →

ARIA Quick Reference

Labeling

  • aria-label
  • aria-labelledby
  • aria-describedby

States

  • aria-expanded
  • aria-selected
  • aria-current
  • aria-disabled
  • aria-hidden

Properties

  • aria-controls
  • aria-haspopup
  • aria-required
  • aria-invalid

Live Regions

  • aria-live
  • aria-atomic
  • role="alert"
  • role="status"

Relationships

  • aria-owns
  • aria-controls
  • aria-activedescendant

Widget Roles

  • role="tab"
  • role="tabpanel"
  • role="dialog"
  • role="menu"

Next Steps

Now that you understand ARIA, learn how to add structured data to make your content even more machine-readable for agents and search engines.

Continue to Structured Data →