Introduction: accessibility starts with HTML
Maybe you've heard about accessibility because of new regulations like the European Accessibility Act (EAA). Or maybe it came up in a project, and now you're wondering: what does it actually take to build an accessible web application? Do I need extra libraries? Special attributes? A deep understanding of assistive technologies?
The good news is that accessibility isn't some mysterious or overly complex requirement—it's built into the web itself. HTML, when used correctly, already does most of the heavy lifting. But somewhere along the way, we got into the habit of overcomplicating accessibility. Instead of leveraging the power of native HTML elements, developers often reach for WAI-ARIA attributes or third-party libraries to "fix" problems that didn't exist in the first place. The reality is that when we rely too much on ARIA or external tools, we're not just making our code more complex—we're also introducing unnecessary risks. Incorrect ARIA implementations can break accessibility rather than improve it, and third-party solutions often introduce unpredictable behavior across browsers and assistive technologies.
Let's get one thing straight—accessibility isn't an afterthought, and it definitely isn't something you sprinkle on at the last minute. It's a fundamental part of web development, ensuring that digital experiences work for everyone, regardless of how they interact with the web.
The ustwo Inclusivity Principles
At the start of this series, we introduced the ustwo Inclusivity Principles, but to recap, the principles are:
- Level-up your gear
- Enjoy the patterns
- Think beyond touch
- Close your eyes
- Party party (test) party!
In short, teams who expertly wield the best tools, follow tried-and-tested patterns and test regularly with keyboards and screen readers will deliver work that is accessible to everyone.
In the context of these principles, HTML is a tool, and using it is a pattern. In this article, we'll explore why native HTML should always be your first choice for accessibility, where ARIA fits in (and where it doesn't), and how you can build accessible, maintainable UI components—without unnecessary complexity.
The golden rule: use native HTML elements whenever possible
If there's one thing you take away from this article, let it be this: always use native HTML elements before considering ARIA or external libraries.
Browsers and assistive technologies have spent decades refining how built-in elements behave. Buttons, links, form inputs, and interactive elements like <details>
and <dialog>
all come with accessibility baked in—meaning they support keyboard navigation, focus management, and screen readers without any extra effort.
But don't just take my word for it—let's prove it with real-world examples. In this article, we'll explore common UI components and show you how they can be fully accessible using nothing but HTML. No fancy ARIA hacks, no dependencies, just pure, simple, native HTML. Let's get started.
The first rule of ARIA: don't use ARIA
If you've ever looked into accessibility, you've probably come across WAI-ARIA (Web Accessibility Initiative - Accessible Rich Internet Applications). It's a specification designed to improve accessibility when native HTML alone isn't enough. But here's the thing—ARIA is not a shortcut for accessibility, and when misused, it can actually make things worse.
There's a well-known saying among accessibility experts: "the first rule of ARIA is: don't use ARIA."
This isn't an anti-ARIA stance; it's a reminder that HTML already provides accessibility out of the box, and ARIA should only be used as a last resort when there are no native alternatives.
When should you actually use ARIA?
ARIA is useful in specific scenarios where HTML doesn't provide built-in semantics or interactivity. A few examples where ARIA is actually necessary (which we'll cover later in this post) include:
- Live regions (
aria-live="polite"
) for announcing dynamic content updates. - Custom interactive widgets like tab interfaces or complex tree views.
- Contextual relationships (
aria-labelledby
,aria-describedby
) when additional explanation is needed beyond what HTML provides.
That said, if an equivalent native HTML element exists, use it instead. ARIA should never be added just for the sake of it—doing so can introduce conflicts, confusion, and extra maintenance.
The risks of using ARIA when you don't need to
Misusing ARIA often results in accessibility regressions rather than improvements. Here's why:
- Screen Reader conflicts – If an element has both native behaviors and ARIA roles, assistive technologies can get confused. This can lead to elements being announced incorrectly or duplicated in accessibility trees.
Take this example:
<button aria-label="Submit form">Submit form</button>
Since the button already has visible text, a screen reader will announce both:
"Submit form, button" "Submit form"
This makes the experience repetitive and frustrating for users relying on assistive tech.
When should you use aria-label? Only when the button has no visible text, like an icon button:
<button aria-label="Search">
🔍
</button>
Rule of thumb: if it's already readable, skip ARIA.
-
Increased complexity – ARIA often requires additional attributes to work properly, like manually handling keyboard navigation or focus states.
-
Developer misinterpretation – Many developers assume adding
role="button"
to a<div>
makes it behave like a button—it doesn't. You still need to manually implement keyboard interactions, focus management, and more.
Modern browsers and assistive technologies already handle this
The reason ARIA is often unnecessary is that modern browsers, screen readers, and operating systems already know how to handle native elements correctly. Buttons, links, forms, and even modals (thanks to <dialog>
) come with built-in accessibility support, requiring zero additional markup or scripting.
If you're thinking about adding ARIA, stop and ask yourself:
- Is there a native HTML element that already provides this functionality?
- Will adding ARIA require extra JavaScript to maintain expected behavior?
- Have I tested this with a screen reader and keyboard navigation?
Chances are, you don't need ARIA at all.
Final thought: ARIA is a last resort, not a first choice
ARIA is a powerful tool—but like all tools, it needs to be used correctly. Before reaching for ARIA, trust HTML. It's been designed from the ground up to be accessible. The less we rely on ARIA for things that HTML already handles, the better experience we'll create for everyone.
Interactive components: do you really need a library?
One of the most common misconceptions in web development is that interactive components require JavaScript-heavy libraries to be accessible and functional. But in reality, HTML provides built-in elements that handle accessibility for us—no extra scripts, no ARIA hacks, no unnecessary complexity.
Let's explore some of the most overengineered UI components and how native HTML solutions make them simpler, more accessible, and easier to maintain.
Dropdowns: the <select>
element vs. a custom dropdown
Dropdowns are a classic case of overcomplication. Many developers opt for custom dropdowns made with <div>
elements, JavaScript, and ARIA roles—only to spend hours debugging focus states, keyboard navigation, and screen reader behavior.
The wrong approach: a custom dropdown
This version requires extra JavaScript to handle selection, focus, and keyboard interaction. It also needs ARIA roles to even be recognized as a dropdown by screen readers.
<div class="dropdown">
<button id="dropdownButton" aria-haspopup="true" aria-expanded="false">
Select an option
</button>
<ul class="dropdown-menu" role="menu">
<li role="menuitem">Option 1</li>
<li role="menuitem">Option 2</li>
<li role="menuitem">Option 3</li>
</ul>
</div>
<script>
const button = document.getElementById('dropdownButton');
const menu = document.querySelector('.dropdown-menu');
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true' || false;
button.setAttribute('aria-expanded', !expanded);
menu.style.display = expanded ? 'none' : 'block';
});
</script>
The right approach: the native <select>
element
The <select>
element does everything a dropdown needs by default:
- Fully accessible with screen readers.
- Supports keyboard navigation out of the box.
- Works seamlessly across all browsers and devices.
<label for="dropdown">Choose an option:</label>
<select id="dropdown">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
Before you reach for a custom component or install yet another library, here's what you get for free just by using the native <select>
:
- No JavaScript required.
- Keyboard and touch-friendly.
- Works consistently across different platforms.
Accordions: the <details>
and <summary>
elements
A common UI pattern for FAQs and expandable sections is the accordion. Many implementations rely on JavaScript to toggle visibility, along with ARIA attributes like aria-expanded
and aria-controls
. But HTML has a built-in alternative: the <details>
and <summary>
elements.
The Wrong Approach: A Custom Accordion
This approach requires JavaScript to manage the expanded/collapsed state, focus behavior, and screen reader announcements.
<div class="accordion">
<button class="accordion-header" aria-expanded="false">Click to expand</button>
<div class="accordion-content" hidden>
<p>This content was hidden but is now visible!</p>
</div>
</div>
<script>
document.querySelector('.accordion-header').addEventListener('click', function () {
const content = this.nextElementSibling;
const expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !expanded);
content.hidden = expanded;
});
</script>
The right approach: the native <details>
and <summary>
With just HTML, you get:
- Built-in keyboard support—users can expand/collapse with Enter or Space.
- Automatic screen reader announcements.
- No JavaScript necessary.
<details>
<summary>Click to expand</summary>
<p>This content was hidden but is now visible!</p>
</details>
Want an accessible accordion without reinventing the wheel? The native <details>
tag has your back—no extra code, no surprises:
- Expandable/collapsible behavior with zero JavaScript.
- Works natively with screen readers and keyboards.
- Reduces maintenance complexity.
Modals: the <dialog>
element vs. a custom dialog
Building a fully accessible modal is one of the hardest UI challenges. Developers often create custom modals using <div>
elements with ARIA roles, JavaScript for focus trapping, and event listeners for closing behavior.
But HTML now includes <dialog>
, which solves all of this for you.
The wrong approach: a custom modal
This version requires ARIA roles, JavaScript event listeners, and manual focus management.
<div class="modal" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
<h2 id="modalTitle">Modal Title</h2>
<p>This is a custom modal.</p>
<button class="close-modal">Close</button>
</div>
<script>
const modal = document.querySelector('.modal');
const closeModalButton = document.querySelector('.close-modal');
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
modal.setAttribute('aria-hidden', 'true');
}
});
closeModalButton.addEventListener('click', () => {
modal.setAttribute('aria-hidden', 'true');
});
</script>
The right approach: the native <dialog>
The <dialog>
element:
- Automatically traps focus inside when opened.
- Supports the Escape key for easy closing.
- Works seamlessly with screen readers without extra attributes.
<dialog id="modal">
<h2>Modal Title</h2>
<p>This is a native modal.</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
<button id="open-modal" aria-haspopup="dialog">Open Modal</button>
<script>
const modal = document.getElementById('modal');
document.getElementById('open-modal').addEventListener('click', () => modal.showModal());
</script>
Instead of spending time re-creating modal logic, use <dialog>
—it just works.. And just like that, you've got:
- Focus trapping built-in.
- No need for ARIA hacks or extra event listeners.
- Works with assistive technologies out of the box.
Less code, more accessibility
Most interactive components already exist in HTML, and they work better than their JavaScript-heavy counterparts. Next time you're about to build a dropdown, accordion, or modal from scratch, pause and ask: "Can HTML do this for me?" Chances are, it can.
When WAI-ARIA is actually necessary
So far, we've emphasized that native HTML should always be the first choice for accessibility. However, there are cases where WAI-ARIA is necessary because HTML alone doesn't provide the required functionality.
Let's go over some real-world scenarios where ARIA plays a crucial role.
1. Live regions for dynamic updates
Sometimes, web applications need to dynamically update content without forcing a full page reload. The problem? Screen readers don't always announce these changes automatically. That's where aria-live
comes in.
A common example is loading indicators for asynchronous operations. Imagine a search function where results take a few seconds to appear. Without proper announcements, users relying on screen readers may have no indication that the request is being processed.
To improve accessibility, we can use aria-live="polite"
to notify users that a search is in progress and when results are ready—without interrupting their workflow.
<form id="search-form">
<label for="search">Search:</label>
<input type="text" id="search" required>
<button type="submit">Search</button>
</form>
<p id="status-message" aria-live="polite"></p>
<script>
document.getElementById("search-form").addEventListener("submit", (event) => {
event.preventDefault(); // Evita recarregar a página
const statusMessage = document.getElementById("status-message");
statusMessage.textContent = "Searching...";
setTimeout(() => {
statusMessage.textContent = "Search complete. Results found.";
}, 2000);
});
</script>
Here's why this simple setup works so well—for both users and assistive tech:
- Follows native HTML behavior – The
<button type="submit">
inside a<form>
ensures proper submission behavior without requiring extra JavaScript. - Screen readers get real-time feedback –
aria-live="polite"
ensures that screen readers announce the search status without interrupting the user.
2. Complex custom widgets with no native equivalent
Some interactive components don't have a native HTML equivalent, forcing developers to create them from scratch. Examples include tooltips, tab interfaces, and multi-level menus.
Look forward to part three of this series (coming soon!) in which we document how we built an accessible hover/tap tooltip component.
3. Describing relationships that HTML alone can't convey
Some UI elements need additional context that isn't obvious from their structure. This is where attributes like aria-describedby
and aria-labelledby
help clarify meaning.
For example, a form input with extra instructions that should be announced to screen readers:
<label for="username">Username:</label>
<input type="text" id="username" aria-describedby="username-hint">
<p id="username-hint">Your username must be at least 6 characters long.</p>
Sometimes, inputs need a little extra explanation—ARIA lets you add it without messing with the visual layout:
- Ensures users receive all necessary instructions.
- Links the input to its description without modifying visual design.
- Prevents confusion for screen reader users.
If you must use ARIA, test it properly
ARIA isn't magic—it requires careful testing to ensure it works as expected.
Before shipping any ARIA-enhanced component, apply the ustwo Inclusivity Principles, specifically:
- Verify with accessibility tools like Lighthouse, axe DevTools, or VoiceOver.
- Test with a keyboard – Can you navigate and interact with it?
- Use a screen reader – Does it announce the content correctly?
Conclusion: accessibility is a team effort
If there's one thing we hope you take away from this article, it's this: accessibility isn't a feature—it's a responsibility. It's not something you can solve with a single tool, a quick ARIA fix, or an external library. It's a fundamental part of building digital products that serve everyone.
We often wish there was a shortcut—a way to instantly make everything accessible by dropping in a library or tweaking a few attributes. But the reality is there is no single solution to accessibility. Much like performance, security, or design, accessibility requires a consistent, thoughtful approach at every stage of development. Anyone could "just use Radix"—but accessibility is so much more than that.
At ustwo we follow the ustwo Inclusivity Principles, which have been tried and tested across multiple teams and projects. These principles guide us in making accessibility an integral part of our process, ensuring that our products are usable, compliant, and genuinely inclusive.
No shortcuts, just good practices
The truth is, building accessible products takes effort, discipline, and commitment. But it's worth it—because when we build with accessibility in mind, we're not just avoiding legal risks or ticking compliance boxes. We're making the web better for everyone.
So here's our challenge to you: commit to accessibility as a core principle. Use the right tools, but don't stop there. Follow the principles, test with real users, and make accessibility a habit—not an afterthought.
This is exactly why we advocate for a native HTML-first approach to accessibility. By relying on semantic HTML, rather than overcomplicating things with unnecessary JavaScript frameworks or ARIA hacks, we ensure that web experiences are simpler, more robust, and accessible by default. We're not alone in this thinking—there's a growing movement around HTML First, which aligns closely with this mindset. If you're interested in exploring this approach beyond accessibility and into the broader discussion of reducing JavaScript complexity, check out HTML First.
Because in the end, the web was meant to be for everyone. When we build with accessibility in mind, we're not just meeting requirements—we're making digital experiences easier, clearer, and more inclusive for all users.
Accessibility isn't just for a select few; it benefits everyone—from someone using a screen reader to someone navigating with a keyboard, from a power user multitasking to a person on a slow connection.
So let's commit to building a web that truly works for everyone, everywhere, all the time.