ARIA Roles Explained: When and How to Use ARIA in HTML
ARIA (Accessible Rich Internet Applications) gives developers a powerful toolkit for making dynamic, complex web interfaces accessible to screen reader users — but misuse is rampant and costly. This guide breaks down every major ARIA role category, explains the golden rules of ARIA usage, and shows you concrete code examples so you can apply it correctly.
<p>Here is a sobering number: according to WebAIM's analysis of the top one million website homepages, pages that include ARIA attributes average significantly more detectable accessibility errors than pages without ARIA at all. That's not an argument against using ARIA — it's an argument for using it <em>correctly</em>. ARIA is one of the most powerful tools in the modern accessibility toolkit, but it is also one of the most misunderstood. Get it right and you meaningfully open your site to millions of users with disabilities. Get it wrong and you actively make their experience worse.</p>
<h2>What Is ARIA and Why Does It Exist?</h2>
<p>ARIA stands for Accessible Rich Internet Applications. It is a set of HTML attributes, defined by the W3C's Web Accessibility Initiative, that allows developers to communicate semantic information to assistive technologies such as screen readers, braille displays, and voice control software. When a browser renders a page, it builds two parallel structures: the DOM (what you see) and the Accessibility Tree (what assistive technologies read). ARIA attributes let you modify that Accessibility Tree to accurately describe what custom components are and how they behave.</p>
<p>The need for ARIA grew out of a real problem. HTML was designed for documents, not applications. When the web evolved into a platform for rich, interactive experiences — tabbed interfaces, modal dialogs, drag-and-drop, live data feeds — native HTML elements couldn't convey what those components were or how they worked to a screen reader. ARIA fills that gap. As MDN puts it, ARIA "supplements HTML so that interactions and widgets commonly used in applications can be passed to assistive technologies when there is not otherwise a mechanism."</p>
<p>ARIA does not change visual presentation. It does not add behavior. It does not provide keyboard support automatically. It purely modifies what the Accessibility Tree exposes to assistive technology. This is an important distinction — one that causes many of the errors developers make when reaching for ARIA as a shortcut.</p>
<p>The specification is maintained by the W3C as WAI-ARIA, currently at version 1.2, with 1.3 actively in development. It provides an ontology of roles, states, and properties that together describe accessible user interface elements across the full range of modern web patterns.</p>
<h2>The Three Pillars: Roles, States, and Properties</h2>
<p>Before writing a single line of ARIA, you need to understand the three distinct building blocks the specification provides. They are not interchangeable, and conflating them is one of the most common sources of errors.</p>
<p><strong>Roles</strong> define what an element <em>is</em>. A role answers the question: what kind of thing am I looking at? Examples include <code>button</code>, <code>dialog</code>, <code>navigation</code>, <code>tablist</code>, and <code>progressbar</code>. You apply a role with the <code>role</code> attribute: <code><div role='button'></code>. The role communicates the element's purpose to assistive technology so the user knows how to interact with it.</p>
<p><strong>States</strong> describe the dynamic condition of an element — something that changes as the user interacts with the page. The <code>aria-expanded</code> attribute tells a screen reader whether a collapsible section is open or closed. <code>aria-checked</code> reflects whether a custom checkbox is ticked. States must be kept in sync with JavaScript; a static <code>aria-expanded='false'</code> that never changes is not only useless but actively misleading.</p>
<p><strong>Properties</strong> provide descriptive, usually more stable information about an element. <code>aria-label</code> gives an element an accessible name that overrides its visible text. <code>aria-labelledby</code> points to another element whose text serves as the label. <code>aria-describedby</code> links to supplementary descriptive text. <code>aria-required</code> signals that a form field must be filled in. While states are expected to change frequently, properties tend to be set once and left alone — though there are exceptions.</p>
<blockquote>Roles define what an element is. States define how it is behaving right now. Properties provide additional descriptive context. You need all three working together to produce a fully accessible custom component.</blockquote>
<h2>The Golden Rule — and Why It Matters More Than You Think</h2>
<p>The W3C's first rule of ARIA use is unambiguous: if you can use a native HTML element or attribute with the semantics and behavior you need already built in, use it. Do not reach for ARIA first. This is sometimes called the "no ARIA is better than bad ARIA" principle — a phrase that reflects the very real danger of well-intentioned but incorrect ARIA usage.</p>
<p>Native HTML elements carry implicit ARIA semantics for free. A <code><button></code> element is already exposed to the Accessibility Tree as a button. It is already keyboard-focusable. It already fires on both Enter and Space keypress. It already announces its label. The moment you write <code><div role='button'></code>, you have taken on the responsibility of manually recreating all of that behavior — the keyboard handling, the focus management, the state updates — in JavaScript. This is not a theoretical concern. Forgetting keyboard support on a custom button is one of the most common and most harmful ARIA errors in production.</p>
<p>The cases where ARIA is genuinely necessary tend to cluster around a few scenarios: when you are building a complex widget that has no HTML equivalent (a carousel, a combobox with autocomplete, a tree view); when you are remediating legacy markup where restructuring the DOM is cost-prohibitive; when you are building a web component that needs to expose custom semantics; or when browser and assistive technology support for a native element is inconsistent enough that the ARIA equivalent works more reliably in practice.</p>
<p>Outside those scenarios, your first instinct should always be semantic HTML. Use <code><nav></code> instead of <code><div role='navigation'></code>. Use <code><main></code> instead of <code><div role='main'></code>. Use <code><button></code> instead of <code><div role='button'></code>. The native elements are more robust, better supported, and require far less maintenance.</p>
<h2>A Tour of the Major ARIA Role Categories</h2>
<p>The WAI-ARIA specification organizes roles into several categories. Understanding these categories helps you know which role to reach for and when.</p>
<h3>Landmark Roles</h3>
<p>Landmark roles mark the major regions of a page, allowing screen reader users to jump directly to key sections using keyboard shortcuts. The most commonly used landmark roles are <code>banner</code>, <code>navigation</code>, <code>main</code>, <code>complementary</code>, <code>contentinfo</code>, <code>search</code>, and <code>form</code>. Every one of these has a direct native HTML equivalent: <code><header></code>, <code><nav></code>, <code><main></code>, <code><aside></code>, <code><footer></code>, and so on. In practice, this means landmark roles are almost always redundant if you are using modern semantic HTML. Add them only when you are stuck with non-semantic markup for structural reasons.</p>
<pre><code><!-- Prefer this -->
<header>
<nav>...</nav>
</header>
<main>...</main>
<footer>...</footer>
<!-- Use ARIA only when you must use divs -->
<div role='banner'>
<div role='navigation'>...</div>
</div>
<div role='main'>...</div>
<div role='contentinfo'>...</div></code></pre>
<h3>Widget Roles</h3>
<p>Widget roles describe interactive components that the user manipulates directly. This is where ARIA does its most important work, because many widget patterns have no native HTML equivalent. Common widget roles include <code>button</code>, <code>checkbox</code>, <code>dialog</code>, <code>menu</code>, <code>menuitem</code>, <code>slider</code>, <code>tablist</code>, <code>tab</code>, <code>tabpanel</code>, <code>tooltip</code>, <code>tree</code>, and <code>combobox</code>.</p>
<p>When you use a widget role, you accept full responsibility for keyboard interaction. The WAI-ARIA Authoring Practices Guide (APG) defines expected keyboard patterns for every widget type — for example, arrow keys to move between tabs, Escape to close a dialog, Home and End to jump to the first and last item in a listbox. Failing to implement these patterns means your component is technically labeled but functionally unusable for keyboard-only users.</p>
<pre><code><!-- A custom tab interface -->
<div role='tablist' aria-label='Account settings'>
<button role='tab' aria-selected='true' aria-controls='panel-profile' id='tab-profile'>
Profile
</button>
<button role='tab' aria-selected='false' aria-controls='panel-security' id='tab-security' tabindex='-1'>
Security
</button>
</div>
<div role='tabpanel' id='panel-profile' aria-labelledby='tab-profile'>
<p>Profile settings content</p>
</div>
<div role='tabpanel' id='panel-security' aria-labelledby='tab-security' hidden>
<p>Security settings content</p>
</div></code></pre>
<h3>Live Region Roles</h3>
<p>Live regions are one of ARIA's most genuinely useful features. They allow assistive technologies to announce dynamic content updates — things like status messages, error notifications, chat messages, and loading indicators — to users who cannot see the screen change. Without live regions, a screen reader user submitting a form might never know whether it succeeded or failed unless focus is explicitly moved to the result.</p>
<p>The core live region roles are <code>alert</code>, <code>status</code>, <code>log</code>, <code>marquee</code>, and <code>timer</code>. The <code>alert</code> role carries an implicit <code>aria-live='assertive'</code> setting, meaning it interrupts the user immediately — appropriate for errors or urgent warnings. The <code>status</code> role uses <code>aria-live='polite'</code>, waiting for the user to finish their current task before announcing — ideal for success messages and progress indicators.</p>
<pre><code><!-- Polite status message for non-urgent feedback -->
<div role='status' aria-live='polite' aria-atomic='true'>
<!-- Dynamically inject text here with JavaScript -->
</div>
<!-- Assertive alert for errors that demand immediate attention -->
<div role='alert'>
Please correct the errors below before submitting.
</div></code></pre>
<p>The key to live regions is that the container must be present in the DOM before the dynamic content is injected. A live region that is created and populated simultaneously is frequently missed by screen readers. Build the container on page load and populate it with JavaScript as events occur.</p>
<h3>Document Structure Roles</h3>
<p>Document structure roles — such as <code>article</code>, <code>list</code>, <code>listitem</code>, <code>table</code>, <code>row</code>, <code>cell</code>, <code>figure</code>, and <code>heading</code> — describe the structural organization of content. Most of these are now superseded by native HTML elements, and MDN notes that the majority of document structure roles "should no longer be used as browsers now support semantic HTML elements with the same meaning." The main exception is when you are working with custom rendering environments, web components, or SVG-based content where native HTML elements are unavailable.</p>
<h2>Essential ARIA Properties Every Developer Should Know</h2>
<p>Beyond roles, several ARIA properties appear constantly in real-world accessibility work. These are the ones you'll reach for most often:</p>
<ul>
<li><strong>aria-label</strong>: Provides an accessible name for an element when no visible text label is available or when the visible text is insufficient. Common use cases: icon-only buttons, search fields with no visible label, and close buttons on modals. Note that <code>aria-label</code> overrides any visible text or native label, so use it carefully on elements that do have visible text.</li>
<li><strong>aria-labelledby</strong>: Points to one or more elements whose text content serves as the accessible name. More robust than <code>aria-label</code> for complex cases because the label text stays synchronized with visible content. Accepts a space-separated list of element IDs, and assistive technologies concatenate the referenced text in order.</li>
<li><strong>aria-describedby</strong>: Links to supplementary description text — not a name, but additional context. Use it to connect form fields to their error messages, or to associate a tooltip with the element it describes. Screen readers typically announce this after the element's name and role.</li>
<li><strong>aria-hidden</strong>: Removes an element entirely from the Accessibility Tree. Invaluable for decorative icons, duplicate content, and visual-only elements that would create noise for screen reader users. Never apply <code>aria-hidden='true'</code> to a focusable element — a user could still tab to it, but would receive no information about it.</li>
<li><strong>aria-expanded</strong>: Communicates whether a collapsible element — a dropdown, accordion, or disclosure widget — is currently open or closed. Must be toggled dynamically with JavaScript; a static value is worse than omitting the attribute entirely.</li>
<li><strong>aria-current</strong>: Indicates the current item within a set, most commonly used for navigation to mark the active page link (<code>aria-current='page'</code>) or the current step in a multi-step process.</li>
</ul>
<h2>Common ARIA Mistakes That Actually Harm Accessibility</h2>
<p>Given that pages with ARIA tend to show more accessibility errors than those without, it's worth being explicit about what goes wrong most often. These are not edge cases — they are patterns that appear in production code every day.</p>
<p><strong>Using ARIA roles on elements with strong native semantics.</strong> Some HTML elements have what the spec calls "strong native semantics" — meanings that are deeply baked into the browser and cannot be safely overridden. Placing an inappropriate role on a <code><button></code> or an <code><input></code> can cause the browser to ignore the ARIA role entirely, or produce contradictory behavior that confuses assistive technologies. The role you declare must be appropriate for the element you put it on.</p>
<p><strong>Forgetting keyboard support with widget roles.</strong> ARIA's <code>role='button'</code> tells a screen reader the element is a button. It does not make the element keyboard-operable. If you use a <code><div></code> with <code>role='button'</code>, you must add <code>tabindex='0'</code> to make it focusable, and you must add event listeners for both the Enter and Space keys. Missing any piece of this breaks the experience for keyboard-only users.</p>
<pre><code><!-- Incomplete and inaccessible -->
<div role='button' onclick='doSomething()'>Submit</div>
<!-- Correct custom button implementation -->
<div
role='button'
tabindex='0'
onclick='doSomething()'
onkeydown='if(event.key==="Enter"||event.key===" ")doSomething()'
>Submit</div>
<!-- Or, the right answer: just use a button -->
<button onclick='doSomething()'>Submit</button></code></pre>
<p><strong>Using aria-hidden on focusable elements.</strong> Applying <code>aria-hidden='true'</code> to a focusable element hides it from the Accessibility Tree but not from keyboard navigation. A keyboard user can still tab to it, receive no information about it, and have no way to know what it does. This is a WCAG 2.1 failure under Success Criterion 4.1.2 (Name, Role, Value).</p>
<p><strong>Stale ARIA states.</strong> Failing to update <code>aria-expanded</code>, <code>aria-checked</code>, <code>aria-selected</code>, and similar states when the UI changes leaves screen reader users with a fundamentally incorrect picture of the interface. A menu that visually opens but whose trigger still reads <code>aria-expanded='false'</code> is actively deceptive.</p>
<p><strong>Redundant roles.</strong> Adding <code>role='navigation'</code> to a <code><nav></code> element, or <code>role='button'</code> to a <code><button></code>, does nothing useful. It clutters the code and can occasionally confuse certain AT combinations. Trust the native semantics.</p>
<h2>ARIA and WCAG: Understanding the Connection</h2>
<p>ARIA is not WCAG. They are separate specifications that work together. WCAG (Web Content Accessibility Guidelines) defines the <em>what</em> — the outcomes required for accessible content. ARIA is part of the <em>how</em> — a technical mechanism for achieving some of those outcomes. The most relevant WCAG success criterion for ARIA is <strong>4.1.2: Name, Role, Value</strong>, which requires that all user interface components have a name, expose their role to assistive technologies, and communicate their state and properties programmatically. ARIA is one of the primary tools for satisfying this criterion for custom components.</p>
<p>ARIA also supports several other success criteria. Landmark roles contribute to 2.4.1 (Bypass Blocks) by enabling skip navigation. Live regions are often the right tool for meeting 4.1.3 (Status Messages) in WCAG 2.1, which requires that status messages be programmatically determinable without receiving focus. Proper use of <code>aria-label</code> and <code>aria-labelledby</code> helps satisfy 2.4.6 (Headings and Labels) and 1.3.1 (Info and Relationships).</p>
<p>It's worth noting that WCAG compliance is increasingly a legal requirement. The European Accessibility Act came into full effect in June 2025, extending mandatory accessibility requirements to a broad range of private sector digital services across EU member states. In the United States, the ADA continues to be interpreted to include web accessibility, and federal requirements under Section 508 apply to government and federally funded organizations. Understanding ARIA correctly is not just best practice — it is increasingly part of your compliance obligations.</p>
<h2>Testing Your ARIA Implementation</h2>
<p>The only way to know whether your ARIA implementation actually works is to test it with real assistive technologies. Automated tools like axe, WAVE, and Lighthouse can catch structural violations — a missing required property, an invalid role, an <code>aria-hidden</code> applied to a focusable element — but they cannot tell you whether a screen reader announces your modal in a way that makes sense, or whether keyboard navigation through your custom tree widget follows expected patterns.</p>
<p>For manual testing, the main screen readers to cover are JAWS and NVDA on Windows (together they represent the vast majority of desktop screen reader usage), and VoiceOver on macOS and iOS. TalkBack covers Android. Each combination of screen reader and browser can behave differently, so testing across at least two combinations is strongly recommended. Test every interactive state: open the dialog, expand the accordion, select the option, trigger the alert. Confirm that the announcement matches what a sighted user would understand from looking at the same interface.</p>
<p>When testing custom widgets, work through the keyboard interaction model defined in the WAI-ARIA Authoring Practices Guide for that widget type. If your tablist doesn't respond to arrow keys, or your dialog doesn't trap focus, those are failures regardless of how correct the ARIA markup looks in an automated audit.</p>
<h2>Key Takeaways</h2>
<ul>
<li><strong>Always prefer semantic HTML over ARIA.</strong> Native elements like <code><button></code>, <code><nav></code>, <code><main></code>, and <code><dialog></code> carry built-in accessibility semantics that are more robust and require far less code than their ARIA-on-a-div equivalents. Reach for ARIA only when native HTML genuinely falls short.</li>
<li><strong>ARIA roles are a promise, not a shortcut.</strong> Applying <code>role='button'</code> or <code>role='dialog'</code> to a custom element commits you to implementing the full keyboard interaction model for that widget type. Roles without matching behavior create confusion and WCAG failures.</li>
<li><strong>Keep ARIA states synchronized with your UI.</strong> Dynamic attributes like <code>aria-expanded</code>, <code>aria-checked</code>, <code>aria-selected</code>, and <code>aria-live</code> content must be updated in JavaScript as the UI changes. A stale state is actively harmful — it communicates the wrong information to the user.</li>
<li><strong>Use live regions for dynamic content updates.</strong> Any content that updates without a page reload — notifications, error messages, loading states, chat feeds — needs an <code>aria-live</code> region or an appropriate role like <code>alert</code> or <code>status</code> so screen reader users receive the same information sighted users see automatically.</li>
<li><strong>Test with real assistive technologies, not just automated tools.</strong> Automated scanners catch structural ARIA errors but cannot validate whether your implementation produces a coherent, usable experience. Manual testing with JAWS, NVDA, and VoiceOver is the only way to close that gap.</li>
</ul>
