ARIA Attributes Explained: Roles, States & Properties Guide
ARIA roles and attributes can make complex UIs accessible to screen readers, but misuse makes things worse. Learn when to use ARIA, when to skip it, and the most common mistakes to avoid.
The first rule of ARIA: don't use ARIA
That might sound contradictory for an article about ARIA, but it's the official guidance from the W3C's own documentation. The first rule states: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so."
ARIA (Accessible Rich Internet Applications) is a set of attributes you can add to HTML elements to communicate information to assistive technologies. It bridges the gap between what native HTML can express and what complex web applications need. But ARIA doesn't change how an element actually works - it only changes how it's announced to screen readers.
This distinction is critical. A <div role="button"> will be announced as a button, but it won't be focusable with Tab, won't respond to Enter or Space, and won't submit forms. You'd need to implement all of that behavior yourself. A native <button> does everything automatically.
So when should you actually use ARIA? When native HTML genuinely can't express what you need.
When ARIA is genuinely needed
Despite the "don't use ARIA" rule, modern web applications regularly create interactions that HTML alone can't describe. Here are legitimate use cases:
Custom widgets without native equivalents
HTML has no native tab panel, tree view, combobox with autocomplete, or carousel component. When you build these custom widgets, ARIA roles and properties tell assistive technologies what the component is and how it works.
A tab interface, for example, needs:
-
role="tablist"on the container holding the tab buttons -
role="tab"on each tab button -
role="tabpanel"on each content panel -
aria-selected="true"on the active tab -
aria-controlslinking each tab to its panel
Without these attributes, a screen reader user would just hear a list of buttons with no context about how they relate to the content panels.
Dynamic content updates
When content on a page changes without a full page reload - a notification appearing, a search results list updating, a character count changing - sighted users can see the change. Screen reader users can't, unless you tell their software something changed.
aria-live regions solve this by announcing dynamic content changes:
-
aria-live="polite"- waits for the screen reader to finish its current announcement before reading the update. Use for non-urgent changes like search results or status messages. -
aria-live="assertive"- interrupts whatever the screen reader is currently saying to announce the update immediately. Use sparingly, only for urgent alerts like error messages or time-sensitive warnings.
A common pattern for form validation:
<div aria-live="polite" role="status">3 results found</div>
Providing additional context
Sometimes a visual interface conveys meaning through layout, proximity, or icons that screen readers can't perceive. ARIA provides attributes to fill these gaps:
-
aria-label- provides a text label when there's no visible text. Useful for icon-only buttons:<button aria-label="Close dialog"><svg>...</svg></button> -
aria-describedby- points to an element that provides additional descriptive text:<input aria-describedby="password-hint">with<p id="password-hint">Must be at least 8 characters</p> -
aria-labelledby- similar toaria-labelbut references an existing visible element as the label, which is useful when the label text already exists on the page
Key ARIA roles
ARIA roles tell assistive technologies what an element is. They fall into several categories:
Landmark roles
These identify major page regions, letting screen reader users jump directly to sections:
-
role="banner"- the site header (equivalent to<header>when used as a direct child of<body>) -
role="navigation"- navigation links (equivalent to<nav>) -
role="main"- the primary content area (equivalent to<main>) -
role="contentinfo"- the site footer (equivalent to<footer>when used as a direct child of<body>) -
role="search"- a search form (now available as<search>in HTML) -
role="complementary"- supporting content (equivalent to<aside>)
Notice that most landmark roles have native HTML equivalents. Prefer the native elements when possible - they provide the same semantics without the need for ARIA.
Widget roles
These identify interactive components that don't exist in native HTML:
-
role="tablist",role="tab",role="tabpanel"- tab interfaces -
role="dialog",role="alertdialog"- modal and alert dialogs -
role="menu",role="menuitem"- application-style menus (not navigation menus) -
role="tree",role="treeitem"- expandable tree views -
role="slider"- custom range inputs
State and property attributes
These communicate the current state of interactive elements:
-
aria-expanded="true|false"- whether a collapsible section or menu is open -
aria-selected="true|false"- whether an option is selected (tabs, list items) -
aria-hidden="true"- hides an element from assistive technologies while keeping it visually present -
aria-disabled="true"- indicates a disabled state without removing the element from the tab order -
aria-required="true"- marks a form field as required (thoughrequiredattribute on native inputs is preferred) -
aria-invalid="true"- indicates a form field has a validation error
Common ARIA mistakes
Incorrect ARIA is worse than no ARIA at all. These are the mistakes you'll encounter most often:
Redundant ARIA
Adding ARIA roles to elements that already have native semantics is unnecessary and can cause screen readers to announce things twice:
-
<button role="button">- the<button>element already has an implicit button role -
<nav role="navigation">- the<nav>element already has an implicit navigation role -
<a href="/" role="link">- links already have an implicit link role
This is harmless in some screen readers but can cause duplicate announcements in others. The fix is simple: remove the redundant role.
Wrong roles
Using a role that doesn't match the element's actual behavior creates confusing experiences:
- Using
role="button"on a link that navigates to a new page - screen reader users expect buttons to perform actions and links to navigate - Using
role="menu"for a navigation menu -role="menu"is for application-style menus with specific keyboard interaction patterns (arrow key navigation), not for site navigation links - Using
aria-hidden="true"on focusable elements - this creates a confusing disconnect where keyboard users can focus an element that screen readers can't see
ARIA without behavior
Adding role="button" to a <div> makes screen readers announce it as a button, but it doesn't make it behave like a button. You still need to add tabindex="0" for focus, keyboard event handlers for Enter and Space, and appropriate cursor styling. If you're doing all that work, just use a <button> instead.
How to check the ARIA on a page
Writing ARIA and checking ARIA are different jobs, and the second one is where most of an agency's time goes on a site somebody else built. Here is the order that finds the most in the least time.
Read the accessibility tree, not the HTML
The markup tells you what was typed. The accessibility tree tells you what a screen reader will actually get, after the browser has resolved roles, names and states. In Chrome or Edge, open DevTools, select the element, then the Accessibility pane. Firefox has the same thing under its Accessibility tab.
Three fields answer most questions:
- Name. Is it there, and is it what a person would say out loud? An empty name on a control is the single most common real ARIA bug.
-
Role. Does it match what the thing behaves like? A
role="button"that navigates is a wrong role, not a styling choice. -
State. Open the widget and watch. If
aria-expandedstill saysfalsewith the menu visibly open, the attribute was written once and never updated, which is worse than not having it.
The five checks worth doing by hand
- Does every interactive element have a name? Tab through and read the Name field at each stop. Icon-only buttons are where this fails.
- Do the states change? Toggle every expandable, selectable and checkable thing and watch the attribute update in the tree. Static ARIA is the most common failure of all, and no scanner can see it.
-
Is anything hidden that should not be? Search the DOM for
aria-hidden="true"and check nothing focusable sits inside it. A focusable element inside a hidden subtree can be tabbed to and is invisible to the screen reader at the same time. -
Does the role match the keyboard? A role is a promise about behaviour.
role="tablist"promises arrow keys. Our guide to testing keyboard accessibility by hand is the pass that checks whether the promise is kept. -
Did anything get announced twice? A visible label plus an
aria-labelthat says something different, or a live region duplicating a state change, both produce double announcements. You only hear these with a screen reader on.
What an automated check does catch
Plenty, and it is worth running first so the manual pass starts from a shorter list. axe-core, which our scanner runs alongside its own checks, covers the structural mistakes directly. Its ARIA rules include aria-roles, "Ensure all elements with a role attribute use a valid value"; aria-required-attr, "Ensure elements with ARIA roles have all required ARIA attributes"; aria-allowed-attr, "Ensure an element's role supports its ARIA attributes"; aria-valid-attr-value, "Ensure all ARIA attributes have valid values"; aria-required-children and aria-required-parent for roles that must be nested a particular way; and aria-hidden-focus, "Ensure aria-hidden elements are not focusable nor contain focusable elements" (Deque, axe rule descriptions).
Our ARIA checker runs those across every page you add to a site and reports them with the element and the fix, so a handover audit starts with the list already written.
What no automated check can catch
This is the part to be honest with clients about. A scanner can confirm that aria-label exists. It cannot tell you the label is wrong. It can confirm aria-expanded is present and valid. It cannot tell you it never changes, because that needs somebody to open the menu. And it cannot judge whether a role describes the thing at all: role="listbox" on something that behaves like a menu is valid ARIA and a real bug.
So run both. Start with a free accessibility scan to clear the structural errors, then spend the manual time on names, states and behaviour, which is where the ARIA bugs that actually block people live. Automated checks find many real problems, but no scan can confirm that a page conforms to WCAG.
How AccessGuard helps with ARIA
AccessGuard's ARIA checker examines your pages for incorrect ARIA usage - redundant roles on native elements, invalid attribute values, missing required properties for widget roles, and aria-hidden on focusable elements. It helps you catch the mistakes that make ARIA harmful rather than helpful, so you can use it with confidence where it's genuinely needed.