LinkedIn series demo

ARIA: An Overview

ARIA tells assistive technology what your interface is and what it is doing. This page covers what it is, how it reaches a screen reader, the minimum you actually need, its five rules, and four working patterns to try.

What it is

ARIA stands for Accessible Rich Internet Applications. It is a W3C specification that adds roles, states, and properties to HTML.

A role says what an element is, like tab or dialog. A state says what condition it is in, like pressed or expanded. A property adds a relationship or label, like aria-label or aria-controls.

Here is all three on one element. The role comes free with <button>, aria-pressed is the state, aria-label is the property:

<button aria-pressed="false" aria-label="Mute notifications">
  Mute
</button>

Together they form a contract between the page and assistive technology. You describe the interface honestly, and screen readers announce it correctly. ARIA never changes how an element looks or behaves. It only changes how it is announced.

How it reaches a screen reader

Your HTML never talks to a screen reader directly. There are four steps in between:

  1. The browser builds an accessibility tree alongside the DOM. Every node gets a role, a name, states, and properties. Native HTML fills most of it in; ARIA covers the gaps.
  2. ARIA annotates that tree. aria-label="Close dialog" changes zero pixels on screen. It renames the node's entry in the tree from "✕" to "Close dialog".
  3. The operating system exposes the tree through its accessibility APIs. Your page never sees this part.
  4. The screen reader reads the tree and turns it into speech or braille. Flip aria-pressed and the browser fires a state-change event the reader announces. Mark a region aria-live and its text changes fire announcement events.

Watch it yourself: open this page in Chrome DevTools, select any element, and check the Accessibility pane. It shows the computed role, name, and where each name came from. It is the same data the name inspector below reports.

The minimum: name it, role it, state it

Before the rules, here is the smallest useful version of ARIA. Most accessibility bugs are just one of these four missing. Each one is demonstrated live further down the page.

Name it

Every interactive element needs an accessible name. An icon alone is not a name, and neither is a placeholder.

Don't
<button>✕</button>
<input type="email" placeholder="you@example.com">
Do
<button aria-label="Close dialog">✕</button>

<label for="email">Email</label>
<input type="email" id="email">

See the difference in the name inspector: the unnamed versions report no accessible name, the named ones report theirs and where it came from.

Role it

A group of related controls should announce itself as a group, not as a pile of divs.

Don't
<div>
  <h4>Shipping address</h4>
  <input type="text" aria-label="Street">
</div>
Do
<div role="group" aria-labelledby="addr-heading">
  <h4 id="addr-heading">Shipping address</h4>
  <input type="text" aria-label="Street">
</div>

State it

If it toggles, say so. aria-pressed is the difference between "a button" and "a button that is on".

Don't
<button id="notify">Notifications: off</button>
<!-- the label changes, but assistive tech only hears "button" -->
Do
<button id="notify" aria-pressed="false">Notifications: off</button>

Try it in the toggle demo: flipping the button flips aria-pressed, and the announcement follows.

Announce its updates

When content changes on its own, put it in a live region or nobody hears about it.

Don't
<div id="status"></div>
<script>
  // screen readers never announce this
  document.getElementById('status').textContent = 'File saved.';
</script>
Do
<div id="status" aria-live="polite"></div>

Type into the announcer demo to feel the difference between polite and assertive.

The tabs demo puts it all together: roles, names, states, and keyboard support in one widget.

The five rules of ARIA

  1. If native HTML does the job, use it. Don't
    <div role="button" tabindex="0">Sign in</div>
    Do
    <button>Sign in</button>
  2. Do not change native semantics. A heading is a heading. Don't
    <h2 role="button" tabindex="0">Load more</h2>
    Do
    <button>Load more</button>
  3. Every custom ARIA control must work with the keyboard. If you do build a custom widget, it needs Tab access, arrow keys where expected, and visible focus, just like the native version. Don't
    <!-- mouse-only: keyboard users can never reach it -->
    <div class="switch" onclick="toggle()">Notifications</div>
    Do
    <div class="switch" role="switch" aria-checked="false" tabindex="0"
         onclick="toggle()" onkeydown="if (event.key === ' ') toggle()">
      Notifications
    </div>
  4. Never put role="presentation" or aria-hidden="true" on a focusable element. It stays in the Tab order but vanishes for assistive tech. Don't
    <button aria-hidden="true">Save</button>
    Do
    <button>Save</button>
  5. Every interactive element needs an accessible name. An icon alone is not a name. Don't
    <button>✕</button>
    Do
    <button aria-label="Close dialog">✕</button>

Four working demos

The announcer

Screen readers announce changes inside aria-live regions. Type a message, pick a politeness, and send it. The message goes into a real live region on this page, and it is mirrored in the log so sighted visitors can see what a screen reader would say.

<!-- polite waits for a pause, assertive interrupts -->
<div aria-live="polite" id="live-polite"></div>
<div aria-live="assertive" id="live-assertive"></div>

<script>
  document.getElementById('live-polite').textContent = 'File saved.';
</script>
Politeness

Simulated screen-reader announcements

  1. Nothing announced yet. Send a message above.

The toggle

A toggle is a button with a state. aria-pressed tells assistive technology whether it is on or off. This one is a native <button>, so keyboard support is free: Tab to it, then Space or Enter.

aria-pressed="false"

<button aria-pressed="false" id="notify">Notifications: off</button>

<script>
  const notify = document.getElementById('notify');
  notify.addEventListener('click', () => {
    const on = notify.getAttribute('aria-pressed') === 'true';
    notify.setAttribute('aria-pressed', String(!on));
  });
</script>

The tabs

The tab pattern needs role="tablist", role="tab", and role="tabpanel", plus aria-selected on the active tab. Arrow keys move between tabs, Home and End jump to the ends, and only the active tab is in the Tab order.

Rule one of ARIA: if native HTML does the job, use it. A <button> already has a role, a name, and keyboard support. ARIA is for the things HTML cannot say.

<div role="tablist" aria-label="ARIA concepts">
  <button role="tab" id="tab-1" aria-selected="true"
          aria-controls="panel-1">HTML first</button>
  <button role="tab" id="tab-2" aria-selected="false" tabindex="-1"
          aria-controls="panel-2">Roles</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1" tabindex="0">
  ...
</div>

The name inspector

Rule five: every interactive element needs an accessible name. Focus or hover each sample to see its computed role and name. The computation below is simplified, but the sources are the real ones.

<!-- name comes from the label, role from the input type -->
<label for="email">Email</label>
<input type="email" id="email">
<!-- announced as: textbox, "Email" -->

Icon-only button, named with aria-label.

Input with an associated <label>.

Shipping address

Group named with aria-labelledby.

Just some text in a div.

A plain div. No name, no role.

Focus or hover a sample above.

Computed name (simplified): the real algorithm also weighs CSS, hidden content, and language. The sources shown here are the ones that matter most.