UX Design

ARIA Best Practices: When and How to Use It

By EZUD Published · Updated

ARIA Best Practices: When and How to Use It

ARIA (Accessible Rich Internet Applications) is the most misused accessibility technology on the web. When applied correctly, it bridges gaps between complex custom widgets and assistive technology. When applied incorrectly — which is the majority case — it makes pages less accessible than they would be with no ARIA at all. WebAIM’s annual survey consistently finds that pages with ARIA have more detected accessibility errors than pages without it.

The problem is not ARIA itself. It is that developers use ARIA as a first resort instead of a last resort.

The First Rule of ARIA

The W3C states it plainly: 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.

A <button> element already provides:

  • role="button" (implicit)
  • Keyboard focus (native)
  • Activation on Enter and Space (native)
  • Accessible name from text content (native)

A <div role="button" tabindex="0"> provides role only. You must manually add keyboard focus behavior, Enter/Space handlers, and click handlers. Any one of those you forget creates an accessibility gap. Use the native element.

When ARIA Is Necessary

ARIA is necessary when native HTML has no equivalent for the widget you are building:

WidgetWhy ARIA Is NeededKey ARIA Attributes
TabsNo native tab widget in HTMLrole="tablist", role="tab", role="tabpanel", aria-selected, aria-controls
Combobox / AutocompleteNative <select> does not support type-ahead filteringrole="combobox", role="listbox", role="option", aria-expanded, aria-activedescendant
Tree viewNo native hierarchical list widgetrole="tree", role="treeitem", aria-expanded, aria-level
Application menuHTML nav menus are not application-style menusrole="menubar", role="menu", role="menuitem", aria-haspopup
Dialog<dialog> exists now but ARIA adds flexibilityrole="dialog", aria-modal, aria-labelledby, aria-describedby
Live regionsNo native way to announce dynamic contentaria-live, role="alert", role="status", aria-atomic
Disclosure<details>/<summary> exist but may need augmentationaria-expanded, aria-controls

ARIA Attributes: States, Properties, and Roles

Roles

Roles tell assistive technology what an element is. They override native semantics, so use them deliberately:

  • Landmark roles: navigation, main, banner, contentinfo, search, complementary, region. Prefer the HTML equivalents (<nav>, <main>, <header>, <footer>, <search>, <aside>, <section>). See screen reader compatibility for when native elements suffice.
  • Widget roles: button, checkbox, radio, slider, tab, dialog, combobox, etc.
  • Structure roles: heading, list, listitem, table, row, cell.
  • Presentation/none: role="presentation" or role="none" removes semantics. Use on layout tables or decorative wrappers.

States

States describe the current condition of an element and change dynamically:

  • aria-expanded="true|false" — Whether a collapsible section is open.
  • aria-selected="true|false" — Whether a tab, option, or item is selected.
  • aria-checked="true|false|mixed" — State of a checkbox or toggle.
  • aria-disabled="true" — Element is present but not interactive.
  • aria-invalid="true" — Form field has a validation error.
  • aria-hidden="true" — Element is hidden from assistive technology (use with caution).

Properties

Properties describe static characteristics:

  • aria-label="Close dialog" — Accessible name when visible text is absent.
  • aria-labelledby="heading-id" — Points to the element providing the accessible name.
  • aria-describedby="help-text-id" — Points to supplementary description text.
  • aria-controls="panel-id" — Identifies the element this control governs.
  • aria-required="true" — Field is required (prefer HTML required attribute).
  • aria-live="polite|assertive" — Dynamic region announcement behavior.

Common ARIA Mistakes

Redundant ARIA

<!-- Wrong: redundant role on native element -->
<button role="button">Save</button>
<nav role="navigation">...</nav>

<!-- Right: native semantics are sufficient -->
<button>Save</button>
<nav>...</nav>

ARIA Without Behavior

<!-- Wrong: has role but no keyboard handling -->
<span role="button">Delete</span>

<!-- Right: native element provides behavior -->
<button>Delete</button>

Hiding Focusable Elements

<!-- Wrong: hidden from screen readers but still in tab order -->
<button aria-hidden="true">Close</button>

<!-- Right: remove from both -->
<button hidden>Close</button>
<!-- Or make visible to all -->
<button>Close</button>

Overusing aria-label

<!-- Wrong: aria-label conflicts with visible text -->
<button aria-label="Submit form">Save changes</button>
<!-- Screen reader says "Submit form," sighted user sees "Save changes" -->

<!-- Right: let visible text serve as the label -->
<button>Save changes</button>

Empty Live Regions on Load

Live regions must exist in the DOM before content is added. If you add a live region and immediately populate it, the screen reader may miss the announcement. Insert an empty container on page load, then populate it later.

Testing ARIA Implementation

  1. Browser accessibility tree: Chrome and Firefox DevTools both expose the accessibility tree. Inspect any element to verify its computed role, name, and state.
  2. Automated tools: axe DevTools flags invalid ARIA attributes, missing required properties, and redundant roles.
  3. Screen reader verification: The accessibility tree is an approximation. Real screen reader behavior (NVDA, VoiceOver) is the ground truth. Test every custom widget with at least one screen reader.
  4. Keyboard testing: Every ARIA widget must be keyboard-operable. If you added a role, test keyboard interactions against the WAI-ARIA APG patterns.

Key Takeaways

  • Use native HTML elements first — ARIA is a last resort for widgets with no native equivalent.
  • Every ARIA role requires corresponding keyboard behavior; adding a role alone creates a broken promise.
  • Never use aria-hidden="true" on focusable elements.
  • States (aria-expanded, aria-selected, aria-invalid) must update dynamically as the component state changes.
  • Test with the browser accessibility tree inspector and real screen readers.

Next Steps

ARIA rules and usage guidance from the W3C “Using ARIA” document, WAI-ARIA 1.2 specification, and the ARIA Authoring Practices Guide (APG). Error patterns from WebAIM’s WebAIM Million analysis.