ARIA Best Practices: When and How to Use It
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:
| Widget | Why ARIA Is Needed | Key ARIA Attributes |
|---|---|---|
| Tabs | No native tab widget in HTML | role="tablist", role="tab", role="tabpanel", aria-selected, aria-controls |
| Combobox / Autocomplete | Native <select> does not support type-ahead filtering | role="combobox", role="listbox", role="option", aria-expanded, aria-activedescendant |
| Tree view | No native hierarchical list widget | role="tree", role="treeitem", aria-expanded, aria-level |
| Application menu | HTML nav menus are not application-style menus | role="menubar", role="menu", role="menuitem", aria-haspopup |
| Dialog | <dialog> exists now but ARIA adds flexibility | role="dialog", aria-modal, aria-labelledby, aria-describedby |
| Live regions | No native way to announce dynamic content | aria-live, role="alert", role="status", aria-atomic |
| Disclosure | <details>/<summary> exist but may need augmentation | aria-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"orrole="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 HTMLrequiredattribute).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
- Browser accessibility tree: Chrome and Firefox DevTools both expose the accessibility tree. Inspect any element to verify its computed role, name, and state.
- Automated tools: axe DevTools flags invalid ARIA attributes, missing required properties, and redundant roles.
- 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.
- 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
- Review screen reader compatibility for how ARIA attributes translate to screen reader announcements.
- Implement accessible widgets following the W3C WAI-ARIA Authoring Practices Guide.
- Run automated ARIA checks on your codebase.
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.