Overview

ARIA changes what assistive technology announces; it adds no behavior and no keyboard support. WAI-ARIA 1.2 is the current W3C Recommendation (1.3 is a Working Draft as of June 2026), and the ARIA in HTML spec says which roles and attributes are allowed on which elements. Start with a native element and add ARIA only where it falls short.

Follow the four rules of ARIA use

  1. If a native element or attribute has the semantics and behavior you need, use it.
  2. Do not change native semantics unless you must; role="presentation" on a data <table> removes what screen readers need.
  3. Every interactive ARIA control must work with the keyboard.
  4. Never put role="presentation" or aria-hidden="true" on a focusable element; users land on “nothing”.

Also avoid aria-label on a bare <div> or <span>: generic elements cannot be named, so the label is ignored by design.

Prefer native widgets

NeedUse
Action<button>, not <div role="button" tabindex="0">
Checkbox<input type="checkbox">
Modal<dialog> with showModal()
Disclosure<details> and <summary>; add name for an exclusive accordion
Popup or menu surfacethe popover attribute
Navigation<nav>

<dialog> and <details> are Baseline widely available; the details name attribute is newly available (September 2024), as is popover (January 2025). A modal <dialog> supplies the dialog role, inert background, Esc to close, and focus return.

Learn roles, states, and properties

Roles say what a widget is (dialog, tab), and rarely change. States change on interaction (aria-expanded, aria-checked, aria-selected). Properties describe fixed characteristics (aria-label, aria-haspopup, aria-required). Toggle states from script; set properties once.

Announce async updates with live regions

The live region must exist in the DOM before its content changes. role="status" implies aria-live="polite" and aria-atomic="true"; role="alert" implies assertive. Reserve alerts for errors that need immediate attention.

<div role="status">3 results found.</div>

For one-off announcements without a DOM region, element.ariaNotify("Saved") queues speech; pass { priority: "high" } to interrupt. It is Baseline newly available (September 2026), so feature-detect it and keep a live region as the fallback.

Give custom dialogs name, modality, and focus handling

A role="dialog" element needs an accessible name (aria-labelledby), aria-modal="true", focus moved inside on open, focus trapped while open, and focus returned to the trigger on close. aria-modal alone does not trap focus. Prefer <dialog>, which does this natively. A <button command="show-modal" commandfor="dlg"> can open it without script (invoker commands, newly available December 2025).

Wire tabs with roving tabindex

Use role="tablist" on the container, role="tab" on each button, and role="tabpanel" on each panel, linked by aria-controls and aria-labelledby. Only the selected tab is in the Tab order (tabindex="0"); others use tabindex="-1". Arrow keys move between tabs, Home and End jump to the ends, and the panel gets tabindex="0" if it holds no focusable content. Follow the WAI-ARIA Authoring Practices tab pattern exactly.

<div role="tablist" aria-label="Product features">
  <button role="tab" id="t1" aria-selected="true" aria-controls="p1">Overview</button>
  <button role="tab" id="t2" aria-selected="false" aria-controls="p2" tabindex="-1">Specs</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1">Overview content.</div>
<div role="tabpanel" id="p2" aria-labelledby="t2" hidden>Specs content.</div>

Avoid a hand-built listbox or combobox

A custom role="listbox" must handle Up, Down, Home, End, type-ahead, Esc, and aria-activedescendant. A half-built one is worse than a plain <select>. If you cannot commit to the full pattern, style a native <select> or adopt a tested library component. See react for state handling.