Complete HTML Course Module 10: Advanced: Accessibility in HTML

ARIA Explained: Roles, Labels and States

  • Beginner
  • Lesson 10.2
  • 5 min read
  • 46 of 69 in the course

In short

ARIA is a set of HTML attributes that give extra information to screen readers when normal HTML is not enough. In this lesson you will learn ARIA roles, labels and states, and when you should not use them. What is ARIA and what is the first rule of ARIA?

ARIA is a set of HTML attributes that give extra information to screen readers when normal HTML is not enough. In this lesson you will learn ARIA roles, labels and states, and when you should not use them.

What is ARIA and what is the first rule of ARIA?

ARIA stands for Accessible Rich Internet Applications. It is a set of attributes that start with aria- plus the role attribute. ARIA changes what a screen reader announces. It does not change how the element looks or behaves. A <div role="button"> is still a <div>: it cannot get keyboard focus and does not react to the Enter key.

That is why the first rule of ARIA is: if a native HTML element already does the job, use it instead of ARIA. A real <button> is always better than <div role="button">. People who build accessibility tools often say "No ARIA is better than bad ARIA", because wrong ARIA can make a page worse than no ARIA at all.

Instead of thisUse this
<div role="button"><button>
<span role="link"><a href="...">
<div role="navigation"><nav>
<div role="heading" aria-level="2"><h2>
<div role="checkbox"><input type="checkbox">

ARIA labels: aria-label, aria-labelledby and aria-describedby

Every interactive element needs an accessible name, which is what a screen reader says when the user reaches it. Usually the name comes from visible text, like the text inside a <button> or a <label> for an input. When there is no visible text, ARIA can help.

  • aria-label gives a name directly as text. Use it for icon-only buttons.
  • aria-labelledby points to the id of another element whose text should be the name.
  • aria-describedby points to extra help text that is read after the name, like a hint.
<!-- Icon-only button: no visible text, so we add a name -->
<button type="button" aria-label="Close menu">
  <svg width="24" height="24" aria-hidden="true">
    <path d="M4 4 L20 20 M20 4 L4 20" stroke="currentColor" stroke-width="2"/>
  </svg>
</button>

<!-- A section named by its own heading -->
<section aria-labelledby="reviews-title">
  <h2 id="reviews-title">Customer reviews</h2>
  <p>4.8 out of 5 from 320 reviews.</p>
</section>

<!-- A password field with a hint -->
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-hint">
<p id="password-hint">At least 12 characters, with one number.</p>

The close button shows only an X icon, but a screen reader says "Close menu, button". The icon itself has aria-hidden="true" so it is not read twice. The section is announced as "Customer reviews, region". When the user reaches the password field they hear "Password, edit text, at least 12 characters, with one number".

Common mistake: Adding aria-label to a <div> or <span> that has no role. Screen readers often ignore labels on plain, non-interactive elements. Put labels on buttons, links, form fields, landmarks and other elements with a real role.

ARIA states: aria-expanded, aria-hidden and aria-live

A state is information that can change, like "open" or "closed". ARIA states let a screen reader know what is happening right now.

  • aria-expanded="true" or "false" tells the user whether a menu or panel controlled by a button is open.
  • aria-hidden="true" hides an element from screen readers but keeps it visible on screen. Use it for decorative icons and duplicated content.
  • aria-live turns an area into a live region. When its text changes, the screen reader reads the new text without the user moving there. aria-live="polite" waits until the user is idle. aria-live="assertive" interrupts at once, so save it for urgent errors.
<button type="button" aria-expanded="false" aria-controls="mobile-menu">
  Menu
</button>
<ul id="mobile-menu" hidden>
  <li><a href="/shop">Shop</a></li>
  <li><a href="/about">About</a></li>
</ul>

<p>
  <span aria-hidden="true">★★★★☆</span>
  <span class="visually-hidden">Rated 4 out of 5</span>
</p>

<div aria-live="polite" id="cart-status"></div>

The menu button tells screen readers "Menu, collapsed". Your JavaScript must change aria-expanded to "true" and remove hidden when the menu opens, because ARIA never does this for you. The star rating hides the star symbols (a screen reader would say "black star, black star...") and gives a text version instead, which you hide visually with CSS. When JavaScript writes "Added to cart" into #cart-status, the screen reader announces it politely.

Note: A live region must already exist in the page before its text changes. If you add the element and its text at the same moment, many screen readers will say nothing. For simple status messages you can also use role="status", which works like aria-live="polite".

Good vs bad ARIA examples

<!-- Bad: fake button, needs ARIA, tabindex and JavaScript to work -->
<div role="button" onclick="save()">Save</div>

<!-- Good: real button, works with keyboard by default -->
<button type="button" onclick="save()">Save</button>

<!-- Bad: aria-hidden on something the user can focus -->
<a href="/help" aria-hidden="true">Help</a>

<!-- Bad: label repeats the role ("Search button, button") -->
<button aria-label="Search button">Search</button>

<!-- Good: visible text is already the name -->
<button>Search</button>

Never put aria-hidden="true" on something a keyboard user can reach: they would land on an element that the screen reader says nothing about. And don't add the word "button" or "link" to labels, because the screen reader already says the role.

Practice exercise

Build a "Like" button that shows only a heart icon. Give it an accessible name, and add aria-pressed="false" so screen readers know it is a toggle. Then add an empty live region under it where a message like "Added to favourites" could appear.

Hint: aria-pressed works like aria-expanded, but for on/off buttons. Hide the heart icon itself with aria-hidden="true".

Key takeaways

  • ARIA changes what screen readers announce, not how elements behave.
  • First rule of ARIA: use a native HTML element if one exists.
  • aria-label, aria-labelledby and aria-describedby give names and descriptions.
  • aria-expanded, aria-hidden and aria-live share state and changes with screen reader users.
  • Bad ARIA is worse than no ARIA, so add it carefully and test it.