Screen Readers

ARIA Roles and Properties

17 min read

Understanding ARIA Roles and Properties

ARIA (Accessible Rich Internet Applications) provides a way to make web content more accessible to people using assistive technologies. It adds semantic meaning to elements that lack native accessibility. However, ARIA is powerful and can cause harm when misused. This guide provides a comprehensive overview of ARIA and best practices for its use.

The First Rule of ARIA

> "If you can use a native HTML element with the semantics and behavior you require, do so. Don't repurpose an element and add ARIA."

Native HTML elements have built-in accessibility that ARIA can't fully replicate:

html
<!-- Native (preferred) -->
<button>Click me</button>
<input type="checkbox">
<a href="/about">About</a>

<!-- ARIA (only when native isn't possible) -->
<div role="button" tabindex="0">Click me</div>
<div role="checkbox" tabindex="0" aria-checked="false"></div>
<span role="link" tabindex="0">About</span>

ARIA Roles

Roles define what an element is. They're divided into several categories:

Landmark Roles

Help users navigate page structure:

html
<header role="banner">Site header</header>
<nav role="navigation">Main navigation</nav>
<main role="main">Main content</main>
<aside role="complementary">Sidebar</aside>
<footer role="contentinfo">Site footer</footer>
<form role="search">Search form</form>

Note: HTML5 sectioning elements already have implicit roles:

  • <header> → banner (when not inside article/section)
  • <nav> → navigation
  • <main> → main
  • <aside> → complementary
  • <footer> → contentinfo (when not inside article/section)

Widget Roles

For interactive components:

html
<!-- Single-purpose widgets -->
<div role="button">Button</div>
<div role="checkbox" aria-checked="true">Checked</div>
<div role="link">Link</div>
<div role="switch" aria-checked="false">Toggle</div>
<div role="slider" aria-valuenow="50">Slider</div>

<!-- Composite widgets -->
<div role="tablist">
  <div role="tab">Tab 1</div>
  <div role="tab">Tab 2</div>
</div>
<div role="tabpanel">Content</div>

<ul role="menu">
  <li role="menuitem">Item 1</li>
  <li role="menuitem">Item 2</li>
</ul>

<ul role="listbox">
  <li role="option">Option 1</li>
  <li role="option">Option 2</li>
</ul>

Document Structure Roles

html
<div role="article">Article content</div>
<div role="heading" aria-level="2">Heading</div>
<div role="list">
  <div role="listitem">Item</div>
</div>
<div role="img" aria-label="Description">...</div>
<div role="table">
  <div role="row">
    <div role="cell">Cell</div>
  </div>
</div>

ARIA States and Properties

States and properties provide additional information about elements.

States (Change with user interaction)

html
<!-- Boolean states -->
<button aria-pressed="true">Toggle (pressed)</button>
<button aria-expanded="false">Menu (collapsed)</button>
<div role="checkbox" aria-checked="true">Checked</div>
<input aria-invalid="true">
<div aria-hidden="true">Hidden from AT</div>
<button aria-disabled="true">Disabled</button>

<!-- Tristate -->
<div role="checkbox" aria-checked="mixed">Indeterminate</div>

Properties (Generally don't change)

html
<!-- Labeling -->
<button aria-label="Close dialog">X</button>
<input aria-labelledby="label-id">
<input aria-describedby="hint-id">

<!-- Relationships -->
<button aria-controls="menu-id">Open menu</button>
<li aria-owns="submenu-id">Has submenu</li>
<div aria-flowto="next-section-id">...</div>

<!-- Live regions -->
<div aria-live="polite">Updates announced</div>
<div aria-live="assertive">Urgent updates</div>
<div role="alert">Error message</div>
<div role="status">Status update</div>

Practical Examples

Accordion

html
<div class="accordion">
  <h3>
    <button
      aria-expanded="true"
      aria-controls="section1-content"
    >
      Section 1
    </button>
  </h3>
  <div
    id="section1-content"
    role="region"
    aria-labelledby="section1-header"
  >
    Content here...
  </div>
</div>
html
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
  aria-describedby="dialog-description"
>
  <h2 id="dialog-title">Confirm Action</h2>
  <p id="dialog-description">Are you sure you want to proceed?</p>
  <button>Confirm</button>
  <button>Cancel</button>
</div>

Custom Dropdown

html
<div class="dropdown">
  <button
    aria-haspopup="listbox"
    aria-expanded="true"
    aria-controls="options-list"
  >
    Select option
  </button>
  <ul
    id="options-list"
    role="listbox"
    aria-label="Available options"
  >
    <li role="option" aria-selected="true">Option 1</li>
    <li role="option" aria-selected="false">Option 2</li>
    <li role="option" aria-selected="false">Option 3</li>
  </ul>
</div>

Progress Indicator

html
<div
  role="progressbar"
  aria-valuenow="75"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-label="Upload progress"
>
  <div style="width: 75%"></div>
</div>

Common ARIA Mistakes

Mistake 1: Overriding Native Semantics

html
<!-- Bad: button is already a button -->
<button role="button">Click me</button>

<!-- Bad: Changing the semantics inappropriately -->
<button role="heading">Not actually a heading</button>

Mistake 2: Missing Required States

html
<!-- Bad: checkbox needs aria-checked -->
<div role="checkbox">Check me</div>

<!-- Good -->
<div role="checkbox" aria-checked="false" tabindex="0">Check me</div>

Mistake 3: Using aria-hidden incorrectly

html
<!-- Bad: Hiding focusable elements -->
<div aria-hidden="true">
  <button>This button is still focusable!</button>
</div>

<!-- Good: Also prevent focus -->
<div aria-hidden="true" inert>
  <button tabindex="-1">Now properly hidden</button>
</div>

ARIA Checklist

  • Native HTML elements used when possible
  • Roles match the element's purpose
  • Required ARIA states are included
  • aria-label or aria-labelledby on interactive elements
  • aria-hidden doesn't hide focusable elements
  • Live regions used for dynamic content
  • Tested with screen readers

Was this article helpful?