Introduction to ARIA: roles, states and the first rule

What ARIA changes, what it cannot do, and why native HTML should always be your first choice.

  • Beginner
  • 7 min read
  • Updated 17 Oct 2026

What you will learn

  • Explain the first rule of ARIA
  • Add states such as aria-expanded
  • Tell roles, states and properties apart
  • Avoid the most common ARIA mistakes

ARIA (Accessible Rich Internet Applications) is a W3C specification of attributes that change how elements are exposed to assistive technology. It lets you describe custom widgets, such as tabs or comboboxes, that HTML has no element for. It is powerful, and it is easy to misuse.

What ARIA does and does not do

ARIA changes only the accessibility tree: what assistive technology is told about an element. It does not change behaviour, appearance or keyboard support.

HTML
<div role="button">Save</div>

A screen reader now announces "Save, button". But the div still:

  • is not focusable with Tab;
  • does nothing when users press Enter or Space;
  • is not submitted with a form or disabled by a disabled attribute.

You have made a promise to the user that the element cannot keep. That is often worse than no ARIA at all, because users expect a button to work like one.

Adding a role is a promise that you have also built the matching keyboard behaviour. If you are not going to write that JavaScript, do not add the role.

The first rule of ARIA

The W3C document Using ARIA sets out five rules. The first matters most:

If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.

NeedUse thisNot this
A button<button><div role="button" tabindex="0">
A link<a href="..."><span role="link">
A checkbox<input type="checkbox"><div role="checkbox" aria-checked="false">
A heading<h2><div role="heading" aria-level="2">
Show and hide<details> and <summary>A custom disclosure, when details fits
A modal<dialog> with showModal()<div role="dialog"> with a hand-built focus trap

The other rules, in short: do not change native semantics unless you really have to; all interactive ARIA controls must be keyboard operable; do not use role="presentation" or aria-hidden="true" on focusable elements; and all interactive elements must have an accessible name.

WebAIM's annual analysis of the top one million home pages has repeatedly found that pages using ARIA tend to have more detected errors than pages without it. ARIA is not the cause in itself; misused ARIA is.

Three kinds of attribute

Roles

A role says what an element is. Native elements have implicit roles: button is a button, nav is navigation, ul is a list. The role attribute overrides or supplies one.

Roles fall into groups:

  • Widget roles: button, checkbox, tab, tabpanel, combobox, slider, switch.
  • Composite roles: tablist, listbox, grid, menu, radiogroup.
  • Document structure roles: heading, list, img, table, tooltip.
  • Landmark roles: banner, navigation, main, region, search.
  • Live region roles: status, alert, log.

States

States describe the current condition and usually change as users interact. You must update them with JavaScript.

  • aria-expanded="true" or "false": a control that shows and hides content is open or closed.
  • aria-pressed="true" or "false": a toggle button is on or off.
  • aria-selected: which tab or option is selected.
  • aria-checked: for custom checkboxes and switches.
  • aria-current="page": the current item in a set, such as the link to this page in navigation.
  • aria-invalid="true": the value fails validation.
  • aria-disabled="true": the control is disabled but stays focusable.

Properties

Properties describe characteristics and relationships that change less often.

  • aria-label, aria-labelledby and aria-describedby: names and descriptions (lesson 8).
  • aria-controls: the element this control affects.
  • aria-haspopup: the control opens a menu, listbox, dialog or similar.
  • aria-live: the element is a live region (lesson 18).
  • aria-hidden="true": remove the element and its children from the accessibility tree.

Example: a toggle button with aria-pressed

HTML has no toggle button, so ARIA fills a real gap. Note that it starts from a native button, which provides focus, keyboard support and the button role. ARIA adds only the state.

HTML
<button type="button" id="mute" aria-pressed="false">Mute notifications</button>

<script>
  const mute = document.getElementById('mute');
  mute.addEventListener('click', () => {
    const pressed = mute.getAttribute('aria-pressed') === 'true';
    mute.setAttribute('aria-pressed', String(!pressed));
  });
</script>
CSS
#mute[aria-pressed="true"] {
  background: #0b0c0c;
  color: #ffffff;
}

A screen reader announces "Mute notifications, toggle button, not pressed", then "pressed" after activation. Keep the label constant. If you change the label to "Unmute" as well, the state becomes contradictory.

Styling from the ARIA attribute, as here, keeps the visual state and the exposed state in step: if you forget to update the attribute, the style breaks too, and you notice.

Example: aria-expanded on a disclosure button

HTML
<button type="button" aria-expanded="false" aria-controls="delivery-info">
  Delivery information
</button>
<div id="delivery-info" hidden>
  <p>Orders placed before 2pm are sent the same day.</p>
</div>

<script>
  const toggle = document.querySelector('[aria-controls="delivery-info"]');
  const panel = document.getElementById('delivery-info');

  toggle.addEventListener('click', () => {
    const expanded = toggle.getAttribute('aria-expanded') === 'true';
    toggle.setAttribute('aria-expanded', String(!expanded));
    panel.hidden = expanded;
  });
</script>

The hidden attribute hides the panel from everyone, including screen readers. aria-controls is supported unevenly, so treat it as a helpful extra, not the main mechanism. Lesson 16 builds full accordions and tabs.

aria-hidden: use with care

aria-hidden="true" hides content from assistive technology while leaving it visible. It is right for decorative icons and duplicated content. It is wrong on anything focusable, because keyboard users can then focus an element that screen readers say does not exist.

HTML
<!-- Good: hides a decorative icon next to visible text -->
<button type="button"><svg aria-hidden="true" focusable="false" width="16" height="16"></svg> Print</button>

<!-- Bad: the link can still receive focus -->
<a href="/offers" aria-hidden="true">Offers</a>

To hide something from everyone, use hidden or display: none. To block a whole region while an overlay is open, use inert.

Common mistakes

  • Adding role="button" to a link or role="link" to a button instead of choosing the right element.
  • Using aria-label on a div or span with no role. Names are not supported on generic elements, so screen readers often ignore it.
  • Copying ARIA from a widget pattern without the keyboard handling it requires.
  • Using role="menu" for website navigation (lesson 17 explains why).
  • Forgetting to update states such as aria-expanded when the UI changes.
  • Redundant roles such as <nav role="navigation"> or <button role="button">. They are harmless but add noise.

Summary

  • ARIA changes only what assistive technology is told; it adds no behaviour.
  • First rule of ARIA: use native HTML when it does the job.
  • Roles say what something is; states and properties describe its condition and relationships.
  • Keep states such as aria-expanded and aria-pressed in sync with the UI, and style from them.
  • Never put aria-hidden="true" on focusable content; use hidden or inert instead.
  • Every ARIA widget must be keyboard operable and named.