Development

ARIA: When to Use It, and Above All When to Skip It

The first rule of ARIA, the roles and states that actually earn their place in JS components, and the anti-patterns that make interfaces worse for screen reader users.

// DD EditorialSep 21, 20267 min read

ARIA has a reputation problem. Developers reach for it as a fix-all (“sprinkle some aria-label on it”), while accessibility specialists keep warning that badly applied ARIA is worse than none. Both reactions make sense once you understand what ARIA actually does: it changes what assistive technology announces, and nothing else. It adds no keyboard support, no focus handling, no behavior. That single fact explains most of the rules below.

What ARIA is, in one paragraph

WAI-ARIA (Accessible Rich Internet Applications) is a W3C specification that lets you describe an element’s role (what it is), its states (checked, expanded, selected) and its properties (its label, what it controls, what describes it) to the accessibility tree. Browsers pass that information to screen readers and other assistive tools. If your markup already carries the right meaning, the accessibility tree is correct without a single ARIA attribute.

The first rule of ARIA

The W3C’s Using ARIA note opens with a rule that sounds almost self-defeating: if a native HTML element or attribute already has the semantics and behavior you need, use it instead of repurposing another element and patching it with ARIA.

Compare the two versions of the same control:

<!-- Native: focusable, Enter and Space work, announced as "button" -->
<button type="button">Save draft</button>

<!-- Rebuilt: you now owe focus, key handling and the role -->
<div role="button" tabindex="0" onclick="save()" onkeydown="handleKey(event)">
  Save draft
</div>

The div version only reaches parity if you remember tabindex, handle both Enter and Space, manage a disabled state and style the focus ring. Miss one and keyboard users are stuck. The native <button> gives you all of it for free.

The same logic applies across the board: <a href> for navigation, <input type="checkbox"> for toggles, <details> and <summary> for simple disclosure, <dialog> for modals, <nav>, <main> and <header> instead of landmark roles. If you are building a component library, bake these choices into the base components; our design systems guide covers why accessibility decisions belong at that level rather than in each screen.

The other four rules, briefly

The same W3C note lists four more rules. They read like common sense, yet each one is regularly broken in production:

  1. Don’t change native semantics unless you really have to. <h2 role="tab"> turns a heading into something that is no longer a heading.
  2. Every interactive ARIA control must be keyboard operable. A role is a promise; the keyboard behavior is on you.
  3. Never put role="presentation" or aria-hidden="true" on a focusable element. Users can tab to it and hear nothing.
  4. Every interactive element needs an accessible name, whether from visible text, a <label>, aria-labelledby or, as a last resort, aria-label.

Where ARIA earns its place

Native HTML still has gaps, especially for the dynamic widgets that JavaScript frameworks make easy to build. This is where ARIA is the right tool:

PatternAttributes that matterWhat they do
Disclosure / accordionaria-expanded, aria-controlsAnnounces open or closed on the trigger button
Tabsrole="tablist", role="tab", role="tabpanel", aria-selectedExposes the tab structure and the active tab
Form errorsaria-invalid, aria-describedbyFlags the field and links it to its error message
Live updatesaria-live="polite", role="status", role="alert"Reads out changes that happen away from the focus
Icon-only buttonsaria-labelGives a name when there is no visible text
Current page in navaria-current="page"Tells users which link they are on

Two details make the difference between working and merely present. First, states must stay in sync with the UI: an accordion that visually opens while aria-expanded stays false announces the opposite of reality. Update the attribute in the same function that toggles the panel. Second, live regions must exist in the DOM before their content changes. Injecting a new element that already contains aria-live and its message is often missed by screen readers.

For French teams working against the RGAA, which builds on WCAG and adds its own test methodology, the ARIA guide by Agence RGAA and its examples (in French) walks through these patterns criterion by criterion, a useful reference when an audit report cites a specific failure.

Anti-patterns that keep showing up in JS components

The clickable div or span. The most common one, usually born in a card component where the whole tile is clickable. Wrap the title in a real link and extend its hit area with CSS instead.

aria-label on a generic element. Putting aria-label on a plain div or span with no role is prohibited by the ARIA specification, and screen readers handle it inconsistently. The label belongs on an interactive element or a landmark.

role="menu" for site navigation. The menu role describes an application menu, like the one in a desktop app, with arrow-key navigation. On a website header it changes how screen readers present the links and creates expectations the component doesn’t meet. A <nav> with a list of links is the correct pattern.

Redundant roles. <button role="button"> or <ul role="list"> add noise without adding meaning. (One exception: some browsers drop list semantics when you remove bullets with CSS, which is why you sometimes see role="list" on purpose.)

Hiding content that is still focusable. An off-canvas menu hidden with aria-hidden="true" but still reachable by Tab creates invisible, silent focus stops. Use inert or display: none on closed panels.

Shouting live regions. aria-live="assertive" or role="alert" on every toast interrupts whatever the user is listening to. Keep assertive announcements for errors that block the task; use polite for the rest.

A label that contradicts the visible text. A button showing “Buy” with aria-label="Add product to basket" breaks voice control: users say what they see and nothing happens. The accessible name should contain the visible text.

A quick check before you ship

Open the accessibility panel in your browser’s DevTools and inspect each custom component: role, name and states should read like a sentence that makes sense. Then tab through it and listen with a screen reader, NVDA on Windows or VoiceOver on macOS. Automated checkers such as axe will catch missing names and invalid attributes, but they can’t tell you whether aria-expanded actually follows the panel. Our WCAG guide lists the free tools worth adding to that routine.

The takeaway is less ARIA, placed with more intent. Start from semantic HTML, add ARIA only where native elements run out, and treat every role you write as a contract your JavaScript has to honor.

DD Editorial
DD Editorial
// DesignerDiscussion editorial team

We test tools, read the docs so you don't have to, and rank the agencies actually shipping great work.

  dd@signal:~ — subscribe.sh
$ ./join --weekly-signal
> one email a week. design intel, dev drops, agency rankings. zero noise.