Best Practices

AI Rules for ARIA Attributes

AI sprinkles ARIA everywhere or ignores it entirely. Rules for the five rules of ARIA, role assignments, state and property attributes, live regions for dynamic content, and common ARIA anti-patterns.

7 min read·April 6, 2025

aria-expanded always 'false' even when the section is open — ARIA that lies is worse than no ARIA

Five rules of ARIA, correct roles, state attributes, live regions, anti-patterns to avoid

AI Either Ignores ARIA or Sprinkles It Like Magic Dust

AI generates ARIA usage with: no ARIA at all (custom widgets that screen readers cannot understand), ARIA on everything (role="button" on a <button> — redundant and confusing), incorrect ARIA (aria-expanded on an element that does not expand anything), conflicting ARIA (role="button" on an <a href> — the link semantics and button role contradict each other), and ARIA without behavior (aria-expanded="true" announced but no keyboard interaction to actually expand). ARIA without correct implementation is worse than no ARIA — it tells screen readers something that is not true.

Correct ARIA usage follows the five rules of ARIA: (1) If you can use a native HTML element, use it instead of ARIA. (2) Do not change native semantics unless absolutely necessary. (3) All interactive ARIA controls must be keyboard-operable. (4) Do not use role="presentation" or aria-hidden="true" on visible focusable elements. (5) All interactive elements must have accessible names. AI violates all five rules by default.

These rules cover: the five rules of ARIA, correct role assignments for custom widgets, state and property attributes, live regions for dynamic content, accessible names, and the most common ARIA anti-patterns.

Rule 1: The Five Rules of ARIA

The rule: 'Rule 1: Use native HTML instead of ARIA when possible. <button> instead of <div role="button">. <nav> instead of <div role="navigation">. <input type="checkbox"> instead of <div role="checkbox">. Native elements provide: keyboard behavior, focus management, form submission, and screen reader semantics — all for free. ARIA roles require you to reimplement all of these manually.'

Rules 2-5: '(2) Do not override native semantics: never <h2 role="tab"> (the heading semantics are lost). Use <div role="tab"> instead. (3) Every interactive ARIA widget must be keyboard-operable: if role="button" then Enter and Space must activate it. If role="tablist" then arrow keys must navigate between tabs. (4) Never aria-hidden="true" or role="presentation" on visible, focusable elements — they become invisible to screen readers but still receive focus (a trap). (5) Every interactive element must have an accessible name: aria-label, aria-labelledby, or visible text content.'

AI generates: <div role="button" onClick={handler}>Submit</div> — violates Rule 1 (use <button>), likely violates Rule 3 (no keyboard handling for Enter/Space), and adds unnecessary ARIA when a native element would work. Replace with <button onClick={handler}>Submit</button> — zero ARIA needed, keyboard behavior included, screen reader announcement correct. The best ARIA is the ARIA you do not need because the native element handles it.

  • Rule 1: native HTML over ARIA — <button> not <div role='button'>
  • Rule 2: do not override native semantics — never <h2 role='tab'>
  • Rule 3: interactive ARIA must be keyboard-operable — role='button' needs Enter/Space
  • Rule 4: never aria-hidden on visible focusable elements — invisible trap
  • Rule 5: every interactive element needs an accessible name — label or aria-label
💡 The Best ARIA Is No ARIA

<div role='button' onClick>: violates Rule 1 (use native), needs manual keyboard handling, needs manual focus management. <button onClick>: zero ARIA needed, keyboard included, screen reader correct. The best ARIA is the ARIA you do not need because the native element handles it.

Rule 2: Correct Role Assignments for Custom Widgets

The rule: 'When native HTML elements cannot represent the widget, assign the correct ARIA role. Common widgets and their roles: custom dropdown → role="listbox" with role="option" children. Tab interface → role="tablist" with role="tab" children and role="tabpanel" for content. Modal dialog → role="dialog" with aria-modal="true". Disclosure (expand/collapse) → button with aria-expanded controlling a region. Toolbar → role="toolbar" with grouped interactive children. Each role has required properties, required keyboard interactions, and expected child roles defined in WAI-ARIA Authoring Practices.'

For role hierarchy: 'Some roles require specific parent-child relationships. role="tab" must be inside role="tablist". role="option" must be inside role="listbox". role="menuitem" must be inside role="menu". Violating the hierarchy: the screen reader does not understand the relationship. A role="tab" outside of a role="tablist" is announced as a tab but has no context (which tablist? how many tabs? which is selected?). The hierarchy provides context that individual roles cannot.'

AI generates: role="menu" for a navigation bar (menus have specific keyboard expectations — arrow keys, Escape, typeahead — that a nav bar does not implement). Or: no roles on a custom dropdown (screen reader announces it as a generic div, not a selectable list). Correct role assignment: the screen reader announces "listbox, 5 options, option 2 of 5 selected." The user understands the widget type, the item count, and their current position — without seeing the screen.

Rule 3: State and Property Attributes

The rule: 'ARIA states communicate the current condition of an element. aria-expanded (true/false): the controlled section is expanded or collapsed. aria-selected (true/false): the tab, option, or item is selected. aria-checked (true/false/mixed): the checkbox is checked, unchecked, or indeterminate. aria-disabled (true): the element is not interactive (but remains visible and focusable — unlike the HTML disabled attribute which removes from tab order). aria-pressed (true/false): the toggle button is pressed (active) or not. Update states dynamically: when the user expands a section, set aria-expanded="true". Screen readers announce the state change.'

For aria-controls and aria-describedby: 'aria-controls="panel-1": the button controls the element with id="panel-1" (screen readers may offer navigation to the controlled element). aria-describedby="error-msg": the input is described by the element with id="error-msg" (screen readers read both the label and the description). aria-labelledby="title-1 subtitle-1": the element is labeled by multiple elements (IDs space-separated — both texts are read as the label). These relationship attributes connect elements that are visually associated but not programmatically linked.'

AI generates: aria-expanded on a button but never updates it (always "false" even when the section is open — screen reader announces "collapsed" when the content is visible). Or: no aria-expanded at all (screen reader does not know the section can be toggled). State attributes must reflect the actual state in real-time. Every user interaction that changes the visual state must update the corresponding ARIA state. The announced state and the visual state must always match.

  • aria-expanded: toggle on expand/collapse — must reflect actual visual state
  • aria-selected: on tabs, listbox options — announce which item is active
  • aria-checked: checkboxes and toggles — true, false, or mixed for indeterminate
  • aria-controls: link button to the panel it controls — screen reader navigation
  • aria-describedby: link input to error message or help text — read as description
⚠️ States Must Reflect Reality

aria-expanded='false' on a button while the section is visually open: the screen reader announces 'collapsed' for expanded content. Every user interaction that changes visual state must update the ARIA state. Announced state and visual state must always match.

Rule 4: Live Regions for Dynamic Content

The rule: 'When content updates dynamically without a page reload, screen readers do not know it changed. aria-live regions announce changes: aria-live="polite" (announce when the user is idle — use for most updates), aria-live="assertive" (announce immediately, interrupting the current announcement — use for critical alerts only), role="alert" (shorthand for aria-live="assertive" + aria-atomic="true" — the entire region is re-announced). Add aria-live to the container before the content changes — the element must exist with aria-live before the dynamic content is inserted.'

For common live region use cases: 'Toast notifications: <div role="alert">Your changes have been saved.</div> (announced immediately). Form validation: <div aria-live="polite" id="email-error">Email format is invalid.</div> (announced when idle — does not interrupt typing). Loading state: <div aria-live="polite">Loading results...</div> then <div aria-live="polite">15 results found.</div> (transition announced). Chat messages: <div aria-live="polite">{lastMessage}</div> (new messages announced as they arrive).'

AI generates: a toast notification that pops up visually but is invisible to screen readers (no aria-live, no role="alert" — the dynamic DOM change is silent). The user saves a form: sighted users see "Saved!" toast. Screen reader users hear nothing — they do not know the save succeeded. With role="alert": the screen reader announces "Your changes have been saved" immediately. One attribute makes dynamic feedback accessible.

ℹ️ One Attribute Makes Feedback Accessible

Toast notification pops up visually but screen readers hear nothing. Add role='alert': the screen reader announces 'Your changes have been saved' immediately. One attribute makes dynamic feedback accessible to every user.

Rule 5: Common ARIA Anti-Patterns to Avoid

The rule: 'Anti-pattern 1: role="button" on <button> (redundant — <button> already has the button role). Anti-pattern 2: aria-label that duplicates visible text (aria-label="Submit" on a button that already says "Submit" — screen reader announces "Submit" twice or the aria-label overrides the visible text). Anti-pattern 3: aria-hidden="true" on content that is visible (hides content from screen readers but visible to sighted users — creates an inconsistent experience). Anti-pattern 4: role="presentation" on interactive elements (removes semantics from a button or link — makes it invisible to assistive technology while remaining visually interactive).'

For the aria-label vs visible text rule: 'aria-label overrides the element visible text for screen readers. <button aria-label="Close">X</button> — screen reader announces "Close button" (good — "X" alone is meaningless). <button aria-label="Submit form">Submit</button> — screen reader announces "Submit form button" which differs from the visible "Submit" text. Voice control users say "Click Submit" but the accessible name is "Submit form" — the command may not match. Use aria-label only when the visible text is insufficient or absent. When visible text is clear, do not add aria-label.'

AI generates: ARIA on everything "just to be safe." role="button" on buttons, role="link" on links, aria-label duplicating visible text, role="img" on <img> elements. Each redundant attribute adds noise. Screen readers process ARIA verbosely — redundant attributes cause double-announcements, conflicting information, and a cluttered experience. The safest ARIA strategy: native HTML first, ARIA only for gaps, test with a screen reader before shipping.

Complete ARIA Attributes Rules Template

Consolidated rules for ARIA attributes.

  • Five rules: native HTML first, do not override semantics, keyboard required, no hidden focusable, name required
  • Correct roles: listbox/option, tablist/tab/tabpanel, dialog, toolbar — per WAI-ARIA Practices
  • States reflect reality: aria-expanded, aria-selected, aria-checked — update on every interaction
  • Live regions: aria-live='polite' for updates, role='alert' for critical — container exists before content
  • No redundant ARIA: do not add role='button' to <button> or aria-label duplicating visible text
  • aria-label only when visible text is insufficient: 'X' needs aria-label='Close', 'Submit' does not
  • Never aria-hidden on visible focusable elements — invisible to screen reader but receives focus
  • Test with screen reader before shipping: NVDA (free, Windows), VoiceOver (built-in, Mac/iOS)