Skip to Content
Native HTML vs. ARIA

Overview

ARIA (Accessible Rich Internet Applications) is a set of attributes that modify how elements appear in the accessibility tree. It was created to bridge gaps where native HTML falls short — complex widgets, dynamic content, and application-like behaviors that HTML alone cannot describe. But ARIA is a double-edged sword: used correctly, it makes complex interfaces accessible; used incorrectly, it actively harms the user experience by providing misleading information to assistive technology.

The W3C’s Using ARIA  specification opens with five rules that every developer should internalize before reaching for any aria-* attribute. These rules exist because the most common accessibility defects found in audits are not missing ARIA — they are incorrect or unnecessary ARIA overriding otherwise functional native HTML.

WCAG Criteria:


The Five Rules of ARIA

Rule 1: Don’t use ARIA if native HTML works

If you can use a native HTML element or attribute with the semantics and behavior you need already built in, use it instead of adding ARIA. Native elements come with keyboard handling, focus management, and screen reader announcements for free. ARIA only modifies the accessibility tree — it does not add behavior.

A <button> is focusable, responds to Enter and Space, and announces its role automatically. A <div role="button"> announces as a button but does nothing else — you must manually add tabindex="0", keydown handlers for Enter and Space, and click handlers. Every piece you add manually is a piece that can break.

Rule 2: Don’t change native semantics unless you really have to

Do not use ARIA to override the native semantics of an element. For example, don’t do this:

<!-- BAD: h2 is now announced as a tab, losing heading semantics --> <h2 role="tab">Section Title</h2>

Instead, nest the element:

<!-- GOOD: the tab is a separate element; the heading retains its role --> <div role="tab"><h2>Section Title</h2></div>

Rule 3: All interactive ARIA controls must be keyboard operable

If you create a widget using ARIA roles (like role="button", role="slider", or role="menuitem"), you are responsible for implementing all expected keyboard interactions. Screen reader users will expect the element to behave according to the ARIA Authoring Practices Guide (APG)  patterns. A button that cannot be activated with Enter or Space, or a listbox that cannot be navigated with arrow keys, is broken.

Rule 4: Don’t use role="presentation" or aria-hidden="true" on focusable elements

Hiding an element from the accessibility tree while it remains focusable creates a disorienting experience. The screen reader user can Tab to the element but hears nothing — or hears a generic announcement with no context. The element exists in one world (keyboard navigation) but not the other (screen reader output).

<!-- BAD: focusable but invisible to assistive technology --> <button aria-hidden="true">Save</button> <!-- BAD: announced as nothing, but still in tab order --> <a href="/settings" role="presentation">Settings</a>

Rule 5: All interactive elements must have an accessible name

Every interactive element needs an accessible name — the text a screen reader announces to identify it. For native elements this often comes automatically from text content, <label>, or alt attributes. For ARIA widgets, you must explicitly provide a name using aria-label, aria-labelledby, or visible text content.


Native HTML vs. ARIA Equivalent

The table below shows common UI needs alongside their native HTML implementation and the ARIA equivalent. In nearly every case, the native HTML version is preferred.

NeedNative HTMLARIA EquivalentUse Native?
Button<button><div role="button" tabindex="0">Yes
Link<a href="..."><span role="link" tabindex="0">Yes
Heading<h1> - <h6><div role="heading" aria-level="2">Yes
Image with alt<img alt="..."><div role="img" aria-label="...">Yes
Navigation<nav><div role="navigation">Yes
Main content<main><div role="main">Yes
Text input<input type="text"><div role="textbox" contenteditable>Yes
Checkbox<input type="checkbox"><div role="checkbox" aria-checked>Yes
Radio group<fieldset> + <input type="radio"><div role="radiogroup"> + <div role="radio">Yes
Required field<input required><input aria-required="true">Yes
Disabled state<button disabled><div aria-disabled="true">Yes
Progress<progress><div role="progressbar" aria-valuenow>Yes

Key takeaway: every row in this table where “Use Native?” says “Yes” represents a case where reaching for ARIA introduces extra work, extra fragility, and extra risk of getting it wrong — for zero functional benefit.


When You DO Need ARIA

ARIA is not useless — it is essential in specific situations. Here are the legitimate use cases:

States that have no native HTML equivalent

Some UI states simply cannot be expressed with HTML attributes alone:

  • aria-expanded — Indicates whether a collapsible section, dropdown, or disclosure widget is open or closed. No HTML attribute conveys this.
  • aria-pressed — Indicates the toggle state of a button (on/off). HTML has no “pressed” concept for buttons.
  • aria-current — Identifies the current item in a set: the current page in navigation, the current step in a wizard, or the current date in a calendar.
  • aria-live — Marks a region whose content updates dynamically, ensuring screen readers announce changes without requiring focus to move. Essential for toast notifications, chat messages, and real-time data.
  • aria-selected — Marks selected items in composite widgets like tabs, listboxes, or grids.
  • aria-invalid — Programmatically marks a form field as having a validation error, beyond what the native :invalid pseudo-class communicates to assistive technology.

Complex widgets with no native equivalent

Some UI patterns have no native HTML element. ARIA is the only way to make these accessible:

  • Combobox (role="combobox") — A text input with an associated popup list of suggestions
  • Tree view (role="tree", role="treeitem") — Hierarchical list with expand/collapse
  • Tab interface (role="tablist", role="tab", role="tabpanel") — While partially expressible with links and sections, the full tab pattern requires ARIA
  • Menu / Menubar (role="menu", role="menuitem") — Application-style menus with arrow key navigation
  • Feed (role="feed") — An infinite-scrolling list of articles
  • Dialog (role="dialog", role="alertdialog") — While <dialog> exists natively, older browser support and complex modal patterns sometimes require ARIA supplementation

Live regions for dynamic content

When content updates on screen without a page reload — a form error appearing, a notification sliding in, a chat message arriving — sighted users see it immediately. Screen reader users have no idea unless the region is marked with aria-live:

<div aria-live="polite" aria-atomic="true"> <!-- Screen reader will announce when this content changes --> 3 items in your cart </div>

Demo: Native Button vs. ARIA Button

Native <button> vs. DIV with role="button"

Inaccessible
<!-- 15+ lines of JS needed to replicate native button behavior --> <div role="button" tabindex="0" onclick="handleSave()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.click();}"> Save Changes </div>
Live Preview
Save Changes
 
Accessible
<!-- Zero extra JS — keyboard, focus, and role are built in --> <button type="button" onclick="handleSave()"> Save Changes </button>
Live Preview
 

What’s different?

Both examples look identical visually and both announce as “Save Changes, button” to screen readers. So why prefer the native version?

  • The <div> version required role="button", tabindex="0", and an onkeydown handler — three things that could be forgotten or implemented incorrectly
  • The <div> version does not participate in form submission, does not respond to the disabled attribute, and is not discoverable via the screen reader’s “list all buttons” shortcut in some configurations
  • The <button> version works with zero additional code — Enter, Space, click, focus, and role are all built in

Demo: ARIA That Makes Things Worse

Bad ARIA vs. No ARIA

Inaccessible
<!-- Link with role="button" — confuses navigation --> <a href="#" role="button">Go to settings</a> <!-- div with role="button" but no tabindex — not keyboard accessible --> <div role="button">Delete Account</div> <!-- aria-hidden on visible error — screen reader users miss critical info --> <p aria-hidden="true">Error: Your session has expired.</p>
Live Preview
Go to settings
Delete Account
Accessible
<!-- Proper link for navigation --> <a href="/settings">Go to settings</a> <!-- Native button — keyboard accessible by default --> <button type="button">Delete Account</button> <!-- role="alert" ensures the error is announced --> <p role="alert">Error: Your session has expired.</p>
Live Preview
Go to settings

Error: Your session has expired.

Three ways bad ARIA harms users:

  1. role="button" on a link — The link now announces as “button” but behaves like a link (right-click to open in new tab, etc.). Screen reader users expecting button behavior (Enter/Space to activate) get link behavior instead. If you need a link, use a link. If you need a button, use a button.

  2. role="button" without tabindex — The div announces as “button” to screen readers browsing in reading mode, but keyboard users can never focus or activate it. The ARIA role is a promise you failed to keep.

  3. aria-hidden="true" on visible content — The error message is visible on screen but completely hidden from the accessibility tree. Screen reader users have no idea an error occurred. This is the opposite of what should happen — error messages should use role="alert" or aria-live to ensure they are announced proactively.


”No ARIA is better than bad ARIA”

This phrase from the W3C specification deserves its own section because it captures the single most important principle. Consider these real-world patterns that actively degrade accessibility:

<!-- HARMFUL: role="button" without keyboard support --> <span role="button" onclick="doSomething()">Click me</span> <!-- Screen reader says "button" but Tab skips it entirely --> <!-- HARMFUL: aria-label contradicting visible text --> <button aria-label="Submit form">Cancel</button> <!-- Sighted users see "Cancel", screen reader users hear "Submit form" --> <!-- HARMFUL: aria-hidden on a focusable input --> <input type="text" aria-hidden="true" placeholder="Search..."> <!-- User can Tab to it but screen reader announces nothing --> <!-- HARMFUL: incorrect role --> <ul role="navigation"> <li><a href="/">Home</a></li> </ul> <!-- Should be <nav> wrapping the <ul>, not role on the <ul> -->

Every one of these would be better with no ARIA at all. The native HTML semantics — even if imperfect — would at least not actively mislead assistive technology.


A Practical Decision Framework

When deciding whether to use ARIA, ask these questions in order:

  1. Does a native HTML element exist for this? If yes, use it. (<button>, <a>, <input>, <select>, <details>, <dialog>, etc.)

  2. Am I building a complex widget with no native equivalent? If yes, follow the ARIA Authoring Practices Guide  pattern exactly, including all keyboard interactions.

  3. Do I need to communicate a state that HTML can’t express? If yes, use the appropriate aria-* attribute (aria-expanded, aria-pressed, aria-current, etc.) on a native element.

  4. Am I adding ARIA to “fix” something? Stop. The fix is almost certainly to use the correct HTML element in the first place.


Resources