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.
role.<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.
Every ARIA decision should pass through these five rules from the W3C ARIA Authoring Practices.
<button> is always better than <div role="button">. The native element brings role, name semantics, focus, and keyboard for free.role="button" on an <h1> erases the heading from the accessibility tree. Wrap or restructure instead.role="button" to a <div> requires tabindex="0" plus Enter/Space handlers. No exceptions.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.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.
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.
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.).
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>.
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.
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.
role="main"), and roles that override important native semantics.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.
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">.
<nav role="navigation">, <main role="main">, <button role="button"> all repeat what the element already says. Strip the redundant role.
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").
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.
widget, roletype, command, composite, input, landmark, section, structure, window — all abstract. Never put them on a real element.
role="application" Unless You Know You Need Itrole="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.
role="dialog" + aria-modal="true" + Focus ManagementThe 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>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.
<div role="button"> Without Keyboard HandlingWhat'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.
role="presentation" on an Interactive ElementWhat'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>.
role="main" on One PageWhat'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.
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.
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 instead | Why |
|---|---|---|
<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 |
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.
(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.
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.
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.
<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."
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.
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.
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.