EthicsComputerCourses
Join training +91 90532 10052

ARIA Basics & the First Rule

Web Accessibility · Lesson 19 of 28 Advanced 7 min read Updated

WAI-ARIA, usually just ARIA (Accessible Rich Internet Applications), is a W3C standard: a set of HTML attributes that add or change information in the accessibility tree. With ARIA you can tell assistive technology that a <div> is a tab, that a panel is expanded, or that a message should be read aloud. The current version is WAI-ARIA 1.2, a W3C Recommendation since June 2023; version 1.3 is still a draft.

ARIA is powerful, and that is exactly why it is dangerous. In this lesson you will learn what ARIA changes and what it does not, the famous first rule of ARIA, how a control's accessible name is worked out, and the mistakes that make pages worse than having no ARIA at all.

What ARIA does and does not do

ARIA attributes come in three groups:

  • Roles say what an element is: role="tab", role="dialog", role="alert".
  • States describe conditions that change: aria-expanded="true", aria-pressed, aria-checked, aria-invalid.
  • Properties describe characteristics and relationships that rarely change: aria-label, aria-labelledby, aria-describedby, aria-controls, aria-required.

ARIA only changes what assistive technology is told. It does not:

  • make an element focusable,
  • add keyboard behaviour,
  • change how it looks,
  • make the browser do anything when it is clicked.

So <div role="button"> is announced as "button", but it still cannot be reached with Tab or pressed with Enter or Space until you add tabindex="0" and keyboard code yourself. The W3C's ARIA Authoring Practices Guide puts it simply: no ARIA is better than bad ARIA, and a role is a promise that you will build the behaviour that goes with it.

The first rule of ARIA

The W3C's guidance on using ARIA lists a few rules. The first one is the most important:

If a native HTML element or attribute already has the meaning and behaviour you need, use it instead of adding ARIA to another element.

Instead ofUse
<div role="button" tabindex="0"><button type="button">
<span role="link"><a href="...">
<div role="checkbox" aria-checked="false"><input type="checkbox">
<div role="heading" aria-level="2"><h2>
<div role="navigation"><nav>
<div aria-required="true"> field<input required>

The other rules follow from it: do not change the meaning of a native element unless you really must (for example, do not put role="heading" on a <button>); every interactive ARIA control must work with the keyboard; never hide a focusable element with aria-hidden="true"; and every interactive element needs an accessible name.

Bad ARIA in action

This example contains three common ARIA mistakes. Run it and use the Tab key, then read the explanation below it.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Bad ARIA</title>
  <style>
    .btn { display: inline-block; padding: 6px 12px; background: #0b5cad; color: #fff; cursor: pointer; }
  </style>
</head>
<body>
  <h1>Bus pass renewal</h1>

  <p>1. <span class="btn" role="button" id="renew">Renew pass</span></p>

  <p>2. <a href="#help" aria-hidden="true">Help with renewal</a></p>

  <p>3. <button type="button" aria-label="Submit form 7B">Renew now</button></p>

  <p id="msg"></p>
  <script>
    document.querySelector("#renew").addEventListener("click", () => {
      document.querySelector("#msg").textContent = "Renewal started.";
    });
  </script>
</body>
</html>
  1. The <span> has role="button", so a screen reader calls it a button, but Tab skips it and Enter does nothing. The role made a promise the code does not keep.
  2. The link has aria-hidden="true". It is removed from the accessibility tree, yet it can still be focused with Tab. A screen reader user lands on "nothing" and hears silence or something confusing.
  3. The button shows "Renew now" but its aria-label replaces that with "Submit form 7B". A voice control user who says "click Renew now" gets no response, which fails 2.5.3 Label in Name.

Each problem is fixed by removing ARIA, not adding more: use a <button>, remove aria-hidden, and delete the aria-label.

How the accessible name is calculated

The accessible name is what a screen reader announces as the name of a control. The browser computes it from several possible sources, roughly in this order of priority:

  1. aria-labelledby: the text of the element or elements whose ids it lists.
  2. aria-label: a text string.
  3. The native label: <label> for form fields, alt for images, <caption> for tables, <legend> for fieldsets.
  4. The element's own content, for roles such as button, link and heading.
  5. The title attribute, as a last resort.

The first source found wins. This explains why aria-label on a button hides the button's visible text from screen readers.

HTML
<!-- Name: "Delete" (content) -->
<button type="button">Delete</button>

<!-- Name: "Delete Kochi trip" (aria-labelledby, combining two elements) -->
<button type="button" id="del-1" aria-labelledby="del-1 trip-1">Delete</button>
<span id="trip-1">Kochi trip</span>

<!-- Name: "Close" (aria-label; the button has no visible text) -->
<button type="button" aria-label="Close">✕</button>

Good ARIA: a disclosure button

ARIA is the right tool when HTML has no attribute for the information you need. A button that shows and hides a panel is a good example: HTML can make the button, but only ARIA can say whether the panel is currently open.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Disclosure Button</title>
</head>
<body>
  <h1>Pune Metro fares</h1>
  <button type="button" id="toggle" aria-expanded="false" aria-controls="fare-details">
    Show fare details
  </button>
  <div id="fare-details" hidden>
    <p>Fares are worked out by distance. Check the official metro website for current fares.</p>
  </div>
  <script>
    const toggle = document.querySelector("#toggle");
    const panel = document.querySelector("#fare-details");
    toggle.addEventListener("click", () => {
      const open = toggle.getAttribute("aria-expanded") === "true";
      toggle.setAttribute("aria-expanded", String(!open));
      panel.hidden = open;
    });
  </script>
</body>
</html>

The native <button> gives role, focus and keyboard support. ARIA adds only the missing piece: aria-expanded tells screen readers "collapsed" or "expanded" every time the state changes. For simple show-and-hide content, <details> and <summary> do this without any script, as you will see in Dialogs & Accordions.

Common mistakes

  • ARIA instead of HTML. role="button" on a <div>, role="link" on a <span>, and so on.
  • Roles without behaviour. Every role you add must come with the keyboard support users expect.
  • aria-hidden="true" on focusable elements, or on a parent of them.
  • aria-label that disagrees with visible text. Keep the visible words in the name.
  • Stale states. aria-expanded="false" on a menu that is visibly open is worse than no attribute.
  • Copying ARIA from old code without understanding it.

Key points

  • ARIA adds roles, states and properties to the accessibility tree, and nothing else.
  • The first rule of ARIA: if native HTML can do it, use native HTML.
  • A role is a promise: you must add the matching keyboard behaviour.
  • The accessible name comes from aria-labelledby, then aria-label, then native labels, then content, then title.
  • Use ARIA for information HTML cannot express, such as aria-expanded, and keep states up to date.

Exercise

Fix this snippet so it follows the first rule of ARIA. Remove every ARIA attribute that native HTML makes unnecessary, and keep only ARIA that adds real information.

HTML
<div role="navigation" aria-label="Main">
  <span role="link" onclick="go('/')">Home</span>
</div>
<div role="button" aria-expanded="false" onclick="toggleFilters()">Filters</div>
<div role="checkbox" aria-checked="true">Show only AC coaches</div>
Show solution
HTML
<nav aria-label="Main">
  <a href="/">Home</a>
</nav>
<button type="button" id="filters-btn" aria-expanded="false" aria-controls="filters">Filters</button>
<input type="checkbox" id="ac-only" checked>
<label for="ac-only">Show only AC coaches</label>

<nav>, <a href>, <button> and <input type="checkbox"> replace four roles. aria-label stays on the navigation to name the landmark, and aria-expanded stays because HTML has no native way to say whether the filters panel is open.

Quick Quiz

1. What is the first rule of ARIA?

2. What does role="button" on a <div> add?

3. A button has visible text "Pay" and aria-labelledby pointing at a heading "Cart total". What is its accessible name?

4. Why is aria-hidden="true" on a focusable link a problem?

Learn Web Accessibility with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call