ARIA's Role in Modern Web Accessibility
WAI-ARIA (Web Accessibility Initiative — Accessible Rich Internet Applications) is a technical specification that addresses a gap in the Web Content Accessibility Guidelines (WCAG). While WCAG primarily targets static content, ARIA focuses on making dynamic interactions — like submitting job applications, checking out from online stores, or booking healthcare appointments — accessible to assistive technology users.
When you use semantic HTML elements like input, select, or button, two things happen automatically. First, information about the element (its role, state, and name) is passed into the DOM and the Accessibility Tree, which assistive technologies read to understand what an element is and how to interact with it. Second, you get keyboard interactivity for free — a checkbox can be toggled with the spacebar, for instance — with behavior that is standardized across browsers and websites.
The problem arises when developers build custom components from <div>s and <span>s, or rely on component libraries that flatten native elements into non-semantic markup. In these cases, additional work is required to expose meaning and keyboard support to assistive technology users. That's where ARIA comes in.
Understanding ARIA Roles
ARIA provides a set of roles and attributes that supply the accessibility tree with information about an element. A critical point to understand: ARIA roles and attributes do not add keyboard interactivity. Adding role="button” to a <div> won't make it respond to the Enter key — you must implement that behavior yourself in JavaScript. The ARIA Authoring Practices Guide documents which keyboard interactions each component pattern should support.
Roles are the foundation for communicating what an element is. Consider a React select component that renders as a series of unrecognizable nested elements:
<div className="dd-wrapper">
<div className="dd-header">
<div className="dd-header-title"></div>
</div>
<div className="dd-list">
<button className="dd-list-item"></button>
<button className="dd-list-item"></button>
<button className="dd-list-item"></button>
</div>
</div>
Assistive technology cannot identify this element's purpose or how to operate it because no ARIA role is present. Adding an appropriate role provides that critical context:
<div className="dd-wrapper" role="listbox">
While listbox may be unfamiliar, a screen reader user will recognize it as a type of select and know how to interact. Using a native <select> would give you this role automatically, but that's not always feasible. When you must use ARIA, ensure the role accurately matches the component's function.
Be aware that ARIA roles override an element's inherent semantic role:
<img role="button">
This markup now describes an image as a button — a transformation with very few legitimate use cases. Overriding native HTML roles should be avoided unless you have a thorough understanding of the implications. A more robust and accessible approach:
<button><img src="image.png" alt="Print" /></button>
<input type="image" src="image.png" alt="Print" />
<button style="background: url(image.png)" />Print</button>
The general rule: if you're building an interactive element without a semantic HTML equivalent — anything constructed from <div> or <span> — it needs an ARIA role so assistive technology can identify it. Consult the ARIA Authoring Practices Guide for component patterns and MDN web docs for the full list of available roles.
ARIA States and Properties
When an element has a state — such as hidden, disabled, invalid, or selected — you must communicate that state to assistive technology users, along with updates when the state changes. While the formal distinction between states and properties is murky, the practical term for both is ARIA attributes.
Common ARIA attributes include:
aria-checked: Set to="true"or="false"to indicate whether checkboxes and radio buttons are checked.aria-current: Identifies the current page within breadcrumbs or pagination.aria-describedby: References the id of an element to provide additional information for a form field — useful for format examples or error messages.
<label for="birthday">Birthday</label>
<input type="text" id="birthday" aria-describedby="date-format">
<span id="date-format">MM-DD-YYYY</span>
aria-expanded: Indicates whether activating a button reveals more content, as with accordions or navigation items with submenus.
<button aria-expanded="false">Products</button>
This markup tells assistive technology that the Products menu opens a submenu. Contrast that with a link implementation:
<a href="https://www.smashingmagazine.com/products/">Products</a>
Using an anchor sets the expectation of navigating to a new page. If the element instead stays on the current page and expands a submenu, using <button> with aria-expanded communicates both the behavior and the interaction model. The choice between <button> and <a>, and the presence of aria-expanded, conveys essential information about how to interact with the element and what happens next.
aria-hidden: Set to="true"or="false"to remove a visible element from the accessibility tree. Use sparingly — there are few scenarios where assistive technology users should be denied equivalent information.
One valid use case: a card component with a linked image and a linked text title pointing to the same destination. Without intervention, a screen reader user hears each link twice. Marking the image link with aria-hidden="true" prevents the duplication. Technically, combining both into a single link is the ideal solution, but production constraints sometimes demand this workaround. It technically breaks the rules of ARIA, but in a way that preserves accessibility when tested with real users.
aria-required: Indicates that a form field must be completed before submission.
Component-specific attribute guidance is available in the ARIA Authoring Practices Guide and through MDN's documentation of states, properties, and roles. Remember: ARIA attributes inform users of a state — they don't create it. Setting aria-checked="true" merely announces that a checkbox is selected; the actual checked state must already be true, or you're making accessibility worse. aria-hidden="true" is the exception: it removes the element from the accessibility tree outright.
Managing Focus for Custom Elements
Every interactive element on a website must receive keyboard focus. Keyboard users and assistive technology that emulates keyboards rely on the tab key and arrow keys to navigate, and use Enter or sometimes the spacebar to activate controls. Anything clickable with a mouse must also be operable from the keyboard.
Building interactivity from non-semantic HTML requires three things:
tabindex="0"to make the element focusable by keyboard or keyboard-emitting devices;- An event listener for key presses on any element that accepts keyboard input;
- An appropriate ARIA role so screen reader users can identify what was built.
Native HTML controls already provide focus, keyboard handling, and inherent roles. These steps apply only to custom elements built from <div> and <span>.
<div tabindex="0" role="button" onclick="doSomething();">
Click me!
</div>
JavaScript must then handle the key presses:
const ENTER = 13;
const SPACE = 32;
// Select your button and store it in ‘myButton’
myButton.addEventListener('keydown', function(event) {
if (event.keyCode === ENTER || event.keyCode === SPACE) {
event.preventDefault(); // Prevents unintentional form submissions, page scrollings, the like
doSomething(event);
}
});
For guidance on which keys to support, reference the ARIA Authoring Practices Guide and follow its keyboard interaction recommendations for the component you're building.
Common ARIA Pitfalls And How To Avoid Them
Some accessibility errors show up over and over in real-world code. These are the ones I see most often, along with ways to prevent them.
Broken aria-labelledby References
A frequent failure is an aria-labelledby attribute pointing to an ID that no longer exists — often removed by a developer who didn’t realize the connection was there. A modal is a typical case: the title is right in the dialog, but the attribute references a removed element. A more robust approach is to make the modal title an <h1> and have aria-labelledby reference that heading, or simply move focus to the heading when the modal opens, provided role="dialog" is set. The goal is to avoid fragile structures that break under unrelated edits.
Focus Not Moved Into Modals
Screen reader users frequently tab through the background page when a modal opens, either unaware a modal appeared or unable to locate its content. Focus trapping can be handled several ways; a newer technique is adding inert to the <main> landmark, assuming the modal lives outside <main>. Browser support for inert has been improving. Lars Magnus Klavenes covers the pattern in detail in “Accessible modal dialogs using inert.”
Redundant Roles On Native Elements
Adding something like <button role="button"> is meaningless. There is one exception: VoiceOver and Safari strip list semantics when list-style: none is applied, because if there’s no visual indication of a list, why announce one? If your user testing shows an announcement matters, you can restore it with an explicit role="list" on the <ul>. As Adrian Roselli notes, not announcing an unstyled list may be insignificant unless testing proves otherwise.
Blanket tabindex="0"
Some developers new to screen readers assume tabbing is the only navigation method and add tabindex="0" everywhere to make elements “accessible.” That’s a misunderstanding — screen reader users navigate with virtual cursors too. If you don’t know how to operate a screen reader, you can’t diagnose usability issues. Work with an experienced screen reader user to identify what actually needs keyboard support.
Child Roles Without Parent Roles
Invalid Parent-Child Structure
ARIA roles often require direct parent-child relationships. For example, role="option" must sit directly inside a role="listbox" parent.
<div role="listbox">
<ul>
<li role="option">
The markup above is invalid because the <ul> sits between the parent and child in the tree. You can fix this by adding role="presentation" to the <ul>, which removes it from the accessibility tree so the option roles connect properly.
Misusing role="menu" For Site Navigation
Website navigation is a table of contents, not an application menu. ARIA menu roles are designed for desktop-style application behavior, not site links. Use <nav> instead, and if you have child navigation links, hide them until a button triggers their display.
<nav aria-label="Main menu">
<button aria-expanded="false">Products</button>
<ul hidden>
<li>Cat pyjamas</li>...
Heydon Pickering’s “Building Accessible Menu Systems” on Smashing Magazine goes deeper into this topic.
Also, if you use <nav> more than once on a page, each instance needs a unique label — otherwise screen reader users must explore every navigation landmark to find the one they want. A simple aria-label on each <nav> resolves this.
<nav aria-label="Customer service">
<ul>
<li><a href="#">Help</a></li>
<li><a href="#">Order tracking</a></li>
<li><a href="#">Shipping & Delivery</a></li>
<li><a href="#">Returns</a></li>
<li><a href="#">Contact us</a></li>
<li><a href="#">Find a store</a></li>
</ul>
</nav>
Validating Your ARIA
Start with automated tools: browser extensions like Axe or WAVE, plus linters such as Axe for Visual Studio Code or ESLint for JSX elements, catch issues as you write code.
Then listen to your page. You wouldn’t ship code without running it in a browser; treat a screen reader the same way. NVDA is free for Windows, VoiceOver is built into macOS and iOS, and TalkBack ships with Android.
Finally, and most importantly, test with assistive technology users. For organizations with any accessibility budget, this should be non-negotiable. Vendors can recruit AT users or run moderated or unmoderated tests — sometimes with turnaround times as short as two days — to support accessibility at scale.
Frameworks And Component Libraries
Component libraries with built-in accessibility can lighten the load when using a web framework. The caveat: claims of accessibility don’t always translate to real usability for assistive technology users. The only reliable check is testing with the people you’re building for.
Reasonable starting points include:
- React Aria
- Vue A11y
- Material Design 3
- Lion
- Open UI
ARIA In Practice
ARIA can feel like a secret language with its own strict rules, but the guidance is straightforward:
- The first rule is “Don’t use ARIA.” A real
<button>beats a<div role="button">every time. - Don’t override native semantics. Instead of
<button role="heading">, use<h3><button>. - Any ARIA interactive element must work with the keyboard.
- Never put
role="presentation"oraria-hidden="true"on a focusable element.<button role="presentation">hides that button from assistive technology users specifically — that’s exclusion, not accessibility. - Every interactive element needs an accessible name. There are several ways to provide one:
<button>Print</button> (the name is the button text)
<div aria-label="Settings"><svg></div> (the aria-label assigns a name)
<div aria-labelledby="myName">
<h1 id="myName">Heading</h1>
</div>
<label for="name">Name</label>
<input type="text" id="name" />
Think of ARIA as a precision tool you bring in for your hardest accessibility problems. The goal is simply that you build things everyone can use.




