How to Use ARIA Roles Correctly: Complete Guide (2026)

Saar Twito10 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is an ARIA Role?

An ARIA role describes what an element IS to assistive technology. role="button"tells a screen reader "treat this as a button" — it gets announced as "button," reachable via the buttons-only navigation shortcut, and expected to respond to Enter and Space. Roles fall into five categories (landmark, widget, structure, live region, abstract) and obey strict rules about which states, properties, and elements they may appear on.

Key Facts (TL;DR)

  • WCAG criterion: WCAG 2.2 Success Criterion 4.1.2 Name, Role, Value (Level A) requires every interactive element to have a programmatically determinable role.
  • Role categories (5): Landmark, Widget, Structure, Live Region, Abstract. Abstract roles are never used directly.
  • First Rule of ARIA: If a native HTML element does the job, use it instead of role.
  • How common is misuse: WebAIM's 2025 Million homepage analysis again found that pages using ARIA averaged more detected accessibility errors than pages without it.
  • Most-cited mistake: <div role="button"> without tabindex or keyboard handlers — visually a button, but unreachable for keyboard users.

Roles are like job titles. A title only works if the person actually does the job. Pinning role="button" on a <div>that can't be focused, can't be activated with Enter or Space, and has no accessible name is the digital equivalent of giving someone a business card for a job they cannot do.

The Five Rules of ARIA (W3C)

Every ARIA decision should pass through these five rules from the W3C ARIA Authoring Practices.

  • 1. If you can use native HTML, do. A real <button> is always better than <div role="button">. The native element brings role, name semantics, focus, and keyboard for free.
  • 2. Don't change native semantics unless you really must. Putting role="button" on an <h1> erases the heading from the accessibility tree. Wrap or restructure instead.
  • 3. All interactive ARIA must be keyboard-accessible. Adding role="button" to a <div> requires tabindex="0" plus Enter/Space handlers. No exceptions.
  • 4. Don't use role="presentation" or aria-hidden="true" on focusable elements. The element stays in the tab order but disappears from the accessibility tree — keyboard users land on something invisible to their screen reader.
  • 5. All interactive elements must have an accessible name.Role + name is the minimum. A button announced as just "button" is broken.

The Five Categories of ARIA Roles

Every role belongs to one of five buckets. Knowing the bucket tells you what the role is FOR, which native HTML element is its preferred replacement, and which states/properties it accepts.

1. Landmark Roles — Page Structure

Define the major regions of a page so screen-reader users can jump between them. Examples: banner, main, navigation, contentinfo, complementary, search, form, region.

Prefer native HTML: <header>, <main>, <nav>, <footer>, <aside>, <form>. These already carry the implicit landmark role.

2. Widget Roles — Interactive Controls

For custom interactive components without a native HTML equivalent. Examples: button, checkbox, switch, menuitem, tab, tabpanel, slider, combobox, dialog, alertdialog, tree, treeitem.

These almost always need tabindex, keyboard handlers, and matching state attributes (aria-checked, aria-expanded, etc.).

3. Structure Roles — Document Organization

Describe non-interactive content structure. Examples: article, list, listitem, heading, row, cell, figure, group.

Prefer native HTML: <article>, <ul>/<ol>/<li>, <h1><h6>, <table>/<tr>/<td>, <figure>.

4. Live Region Roles — Dynamic Updates

Tell assistive tech to announce content that changes without a page navigation. Examples: alert, status, log, marquee, timer.

role="alert" implies aria-live="assertive"; role="status" implies aria-live="polite". Use them for form validation messages, toast notifications, and async progress.

5. Abstract Roles — Never Used Directly

Examples: widget, roletype, command, composite, input, landmark, section, structure, window.

These exist purely to organize the role taxonomy in the spec. Putting role="widget" on a real element is always a bug.

How to Check Your Roles

  • Greadme deep scan — flags abstract roles used directly, missing required keyboard handling on widget roles, duplicate landmarks (e.g. two role="main"), and roles that override important native semantics.
  • Greadme crawler scan — checks roles across every indexable page so a broken modal pattern or tab strip surfaces site-wide instead of one page at a time.
  • Greadme AI visibility analyzer — surfaces how clean role markup affects how AI answer engines parse and cite your pages.
  • Chrome DevTools → Accessibility tab — view the computed accessibility tree for any element. If the role you expect is not there, the role attribute is wrong or being overridden.
  • axe-core (the open-source rules engine) — integrates into Jest, Playwright, Cypress, and Storybook for build-time role validation.
  • Manual screen-reader test — load the page in NVDA, JAWS, VoiceOver, or TalkBack and tab through. If a custom button is announced as "clickable" instead of "button," the role is missing.

Bad vs Good: The Custom Button Problem

The single most-cited ARIA mistake is a clickable <div>. Three correct ways to ship the same UI:

<!-- BAD: looks like a button, broken for keyboards and screen readers -->
<div onclick="save()" class="btn">Save</div>


<!-- GOOD #1: just use a real button (always preferred) -->
<button type="button" onclick="save()" class="btn">Save</button>


<!-- GOOD #2: link styled as a button (when navigation is the action) -->
<a href="/saved" class="btn">View Saved</a>


<!-- GOOD #3: div + role=button done right (only if 1 and 2 are impossible) -->
<div
  role="button"
  tabindex="0"
  onclick="save()"
  onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); save(); }"
  class="btn"
>
  Save
</div>

Pattern #3 is correct, but it's also four lines of code that <button> gives you for free — including disabled state, focus styles, and form submission. Reach for native HTML first.

8 Rules to Use ARIA Roles Correctly

1. Native HTML Element First, Role Second

Before adding role="X", ask whether an HTML element already has the implicit role X. If yes, use it: <button>, <nav>, <main>, <dialog>, <details>/<summary>, <input type="checkbox">.

2. Don't Repeat Implicit Roles

<nav role="navigation">, <main role="main">, <button role="button"> all repeat what the element already says. Strip the redundant role.

3. One Landmark Per Type (Mostly)

Only one <main> / role="main" per page. Multiple <nav> elements are fine, but each one needs a unique aria-label(e.g. "Primary", "Footer", "Breadcrumb").

4. Widget Roles Need Keyboard + State + Name

Adding role="tab" means committing to: tabindex, arrow-key navigation between tabs, aria-selected updates, aria-controls pointing to the panel, and the panel having role="tabpanel" with aria-labelledby. Skipping any one of these breaks the pattern.

5. Don't Use Abstract Roles

widget, roletype, command, composite, input, landmark, section, structure, window — all abstract. Never put them on a real element.

6. Avoid role="application" Unless You Know You Need It

role="application"tells screen readers to stop their normal browse-mode navigation and pass every keypress straight to the page. It's the right call for a fully keyboard-controlled web app (think a code editor) and almost always wrong everywhere else.

7. Modals Need role="dialog" + aria-modal="true" + Focus Management

The role alone is not enough. On open: trap focus inside the dialog, move focus to the first interactive element, and set aria-modal="true". On close: return focus to the trigger.

<div role="dialog" aria-modal="true" aria-labelledby="dlg-title">
  <h2 id="dlg-title">Confirm Delete</h2>
  <p>This cannot be undone.</p>
  <button>Cancel</button>
  <button>Delete</button>
</div>

8. Live Regions Need to Exist Before the Update

A role="status" or role="alert" region must be in the DOM before the content is injected into it. Mounting and populating in the same tick often means the screen reader never sees the change.

Common Role Mistakes and Fixes

Problem: <div role="button"> Without Keyboard Handling

What's happening: The element is announced as a button by screen readers but cannot be reached with Tab and does not respond to Enter or Space.

Fix: Replace with <button>. If that's impossible, add tabindex="0" and an onkeydown handler that triggers on Enter and Space.

Problem: role="presentation" on an Interactive Element

What's happening: A <button role="presentation"> or <a role="presentation"> stays focusable but is hidden from assistive tech. Keyboard users land on something invisible to their screen reader.

Fix: Remove role="presentation". If the element is purely decorative, also remove tabindex and any click handlers — or replace it with a styled <span>.

Problem: Multiple role="main" on One Page

What's happening: Two regions claim to be the main content of the page. Screen-reader users get inconsistent landmark navigation.

Fix: Keep exactly one <main>. If you need multiple top-level regions, use role="region" with distinct aria-labels for the others.

Problem: Invalid or Misspelled Role

What's happening: role="navigaton" (typo), role="widget" (abstract), role="tabbar" (not in the spec). The role is silently ignored and the element falls back to its native semantics.

Fix: Use the canonical role name from the WAI-ARIA spec. Add eslint-plugin-jsx-a11y with the aria-role rule to catch these at build time.

Native HTML Element vs ARIA Role: Which to Pick

Almost every common ARIA role has a native HTML equivalent that does more, with less code. This table covers the substitutions you will reach for most often.

If you reach for…Use this native HTML insteadWhy
<div role="button"><button>Brings focus, Enter/Space activation, disabled state, form submission for free
<div role="checkbox"><input type="checkbox">Native checked state, label association, form submission
<div role="dialog"><dialog> + showModal()Built-in focus trap, Escape to close, top-layer rendering
<div role="navigation"><nav>Implicit landmark; one less attribute
<div role="main"><main>Implicit landmark; browsers enforce one-per-page
<div role="list"> / listitem<ul> / <li>Screen readers announce list count and position
<div role="heading" aria-level="2"><h2>Native heading navigation, document outline
Custom expand/collapse with aria-expanded<details> / <summary>Free open/close behavior, keyboard handling, no JS

FAQ

What is an ARIA role, in one sentence?

An ARIA role is an attribute that tells assistive technology what category of UI element this is — button, checkbox, dialog, navigation, main content — when the underlying HTML tag does not already say so.

What are the Five Rules of ARIA?

(1) Use native HTML if you can; (2) don't change native semantics unless you must; (3) all interactive ARIA must be keyboard-accessible; (4) don't use role="presentation" or aria-hidden="true" on focusable elements; (5) every interactive element needs an accessible name. They come from the W3C ARIA Authoring Practices.

Is <button> really better than <div role="button">?

Yes — by a large margin. <button> is keyboard-accessible by default, supports the disabled attribute, submits forms, fires click on Enter and Space, and gets a default focus ring. The div equivalent requires tabindex, an onkeydown handler, manual disabled styling, and explicit form-submit logic — every one of which is a chance to ship a bug.

What does role="application" do, and when should I use it?

It tells screen readers to disable browse-mode shortcuts inside the region and pass keys directly to the page. Use it only when you are building a fully keyboard-driven, app-like surface (a web-based code editor, a complex graphing tool). On regular content pages it breaks normal screen-reader navigation.

Can I have more than one <nav> on a page?

Yes, but each one needs a distinct accessible name via aria-label (or aria-labelledby) so screen-reader users can tell them apart in the landmarks list. Common labels: "Primary," "Footer," "Breadcrumb."

Do ARIA roles affect AI search engines like ChatGPT and Perplexity?

Indirectly. AI answer engines parse semantic HTML and the accessibility tree to understand what each region of a page is. Clean roles produce a clean accessibility tree, better traditional search rankings, and stronger structured signals. AI engines preferentially cite pages that already rank well, so accessibility quality and AI visibility move together.

What's the difference between a role and an ARIA attribute?

The role says what the element is (button, tab, dialog). ARIA attributes (also called states and properties) describe its current condition or relationships — aria-expanded="true", aria-checked="mixed", aria-controls="menu-1". Each role has a fixed list of which states and properties it supports.

Conclusion

ARIA roles are a labelling system, not a styling system. The job of a role is to tell assistive technology what an element is so it can be announced, navigated, and operated correctly. The first and best move is almost always to reach for the native HTML element that already carries that role — <button>, <nav>, <main>, <dialog>, <details>. The rule for ARIA, as the W3C puts it, is "no ARIA is better than bad ARIA."

Run a Greadme deep scan to see exactly which elements are using invalid roles, which roles override important native semantics, and which widget roles are missing the keyboard handling they require — then fix the templates first.