ARIA Fundamentals: When to Use It and When Not To
AI generated
A11Y
WCAG
Accessibility · WAI-ARIA 1.2 · Web Accessibility · Frontend
ARIA Fundamentals: When to Use It and When Not To
Accessibility tree semantics without replacing native elements

ARIA never overrides appearance or behavior, it only overlays the accessibility tree with roles, states, and properties that screen readers and other assistive technologies read out. Developers who use ARIA without knowing the five ground rules often build interfaces that are less accessible, not more. This article uses a real tab component to show when ARIA is actually necessary and when a native HTML element remains the better choice.

15 min read ARIA · Roles · States · Properties Magento 2.4.8 · Hyvä Theme · Screen Reader

1. What ARIA Actually Does: Accessibility Tree, Not Behavior

ARIA (Accessible Rich Internet Applications) is neither interaction logic nor a styling technology, it is a pure vocabulary of attributes that supplements or overrides the so called accessibility tree, that parallel structure the browser derives from the DOM and that screen readers, voice control, and switch access devices actually read. A role="button" on a <div> changes exactly nothing about the div's behavior: it still cannot be focused via Tab, does not respond to Enter or Space, and fires no click event on keyboard input. All of that has to be rebuilt separately with JavaScript and tabindex.

This exact misunderstanding causes the most common accessibility bugs: developers add ARIA roles and attributes and assume the element now behaves like its native counterpart. In reality, ARIA only describes what the element should be for assistive technologies, the entire keyboard operation, focus order, and state logic remain fully the developer's responsibility. Once that distinction is internalized, it immediately becomes clear why the first and most important rule is to use ARIA only when no native element already provides the semantics out of the box.

2. The Five Ground Rules of ARIA Use

The W3C specification "Using ARIA" defines five rules that should guide every use of ARIA. Rule one is the most important and the most frequently violated: if a native HTML element or attribute already provides the required semantics and behavior, it should be used instead of recreating it with a role, state, or property. A <button> is focusable, keyboard operable, and correctly announced by every screen reader without a single ARIA attribute.

Rule two forbids changing native semantics unless unavoidable: <h3 role="button">, for example, confuses screen reader users because the heading is now announced as a button but keeps neither the keyboard behavior nor the styling of one. Rule three requires that every interactive ARIA role actually be operable with the keyboard. Rule four bans role="presentation" or aria-hidden="true" on focusable elements, because that creates an area that is focusable but invisible to assistive technologies. Rule five mandates that every interactive element have an accessible name, whether through aria-label, aria-labelledby, or visible text content.


<!-- WRONG: a div rebuilt as a button, violates rule 1 -->
<div class="btn-primary" role="button" tabindex="0" onclick="submitForm()">
  Submit
</div>
<!-- Missing: Enter/Space handler, focus style, correct announcement of disabled state -->

<!-- RIGHT: the native element already provides everything -->
<button type="submit" class="btn-primary">
  Submit
</button>

<!-- WRONG: native semantics overridden, violates rule 2 -->
<h3 role="button" onclick="toggleAccordion()">Shipping costs</h3>

<!-- RIGHT: button inside the heading, semantics stay intact -->
<h3>
  <button type="button" aria-expanded="false" aria-controls="panel-shipping">
    Shipping costs
  </button>
</h3>

3. Role Categories: Landmark, Widget, Structure, and Live Region

The WAI-ARIA specification groups roles into several categories, and understanding them makes choosing the right role far easier. Landmark roles like navigation, main, complementary, and contentinfo structure a page at a coarse level and let screen reader users jump directly between regions with a keyboard shortcut, instead of having to search through all the content linearly. In most cases the corresponding HTML5 elements <nav>, <main>, <aside>, and <footer> already deliver this landmark semantics automatically, without needing an explicit role.

Widget roles like tab, tabpanel, slider, combobox, or dialog describe interactive components that have no native HTML equivalent, and additionally require complete keyboard and focus management implemented in JavaScript. Document structure roles like list, listitem, or heading describe static content structures and are almost always redundant once <ul>, <li>, or <h1> through <h6> are already in use. Live region roles like status, alert, and log mark regions whose changes should be announced automatically to screen readers, without moving focus there. Most landmark roles like navigation or main are rarely needed explicitly in practice thanks to <nav> and <main>, whereas widget roles like tablist always require a complete JavaScript pattern, as the example in section 6 shows.

4. States and Properties: aria-* Beyond the Role

Beyond roles, ARIA defines two further attribute classes: states, which change at runtime, such as aria-expanded, aria-selected, or aria-checked, and properties, which mostly describe stable characteristics of an element, such as aria-label, aria-describedby, or aria-required. The distinction is more than academic: states must be updated via JavaScript on every state change, otherwise the information announced to screen readers drifts apart from the actual visible state. An accordion that is visually open but still reports aria-expanded="false" is simply mislabeled for screen reader users.

aria-hidden="true" removes an element entirely from the accessibility tree without hiding it visually, which is ideal for purely decorative icons and dangerous on interactive or focusable elements, because it makes an element visible to sighted users disappear completely for screen readers. aria-live="polite" announces changes once the screen reader has a pause in speech, while aria-live="assertive" interrupts the current announcement immediately and should be reserved only for genuinely urgent messages such as form errors.

5. Native Elements vs. ARIA Patterns: When HTML Is Enough

The rule of thumb "No ARIA is better than Bad ARIA" comes directly from the W3C specification and describes a real danger: incorrectly applied ARIA can make an element that works fine for sighted users completely unusable for screen reader users, whereas without any ARIA at all, at least the native browser semantics still apply. <button>, <a href>, <select>, <input type="checkbox">, and <details> already provide keyboard operation, focus styles, state management, and correct announcement out of the box, and in the vast majority of cases need not a single ARIA attribute.

A common mistake with custom interactive elements: the native focus ring is removed via CSS with outline: none without providing a visible replacement for :focus-visible. Keyboard users lose all visual orientation about which element is currently active, regardless of whether ARIA is set correctly. Custom components should therefore always ship a clearly visible focus indicator that is at least as recognizable as the native browser focus ring.


/* WRONG: focus ring removed entirely, no replacement */
.custom-widget:focus {
  outline: none;
}

/* RIGHT: visible replacement only on keyboard focus */
.custom-widget:focus {
  outline: none;
}
.custom-widget:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
  border-radius: 4px;
}

/* Ensure high contrast even on a dark background */
@media (prefers-contrast: more) {
  .custom-widget:focus-visible {
    outline-width: 3px;
  }
}

6. Practical Example: An Accessible Tab Component

Tabs are a classic among ARIA widget patterns because there is no native HTML element for tab navigation. The WAI-ARIA Authoring Practices pattern requires a structure of role="tablist" for the container, role="tab" for each tab button, and role="tabpanel" for the associated content. Every tab needs aria-selected, marking the currently active tab, as well as aria-controls, linking it to the ID of its panel. Each panel in turn references its tab via aria-labelledby, so screen readers announce which tab a panel belongs to as soon as the panel changes.

Inactive panels get hidden or are removed from the accessibility tree via aria-hidden="true", so their content is not read out accidentally while it stays invisible. Only the currently active tab button remains part of the normal tab order, all inactive tabs get tabindex="-1", navigation between the tabs themselves does not happen via the Tab key but via the arrow keys, as described in the next section.


<!-- Hyva phtml: accessible tab component with Alpine.js -->
<div x-data="{ active: 'description' }">
  <div role="tablist" aria-label="Product details" class="flex gap-2 border-b">
    <button
        role="tab"
        id="tab-description"
        :aria-selected="active === 'description' ? 'true' : 'false'"
        :tabindex="active === 'description' ? 0 : -1"
        aria-controls="panel-description"
        @click="active = 'description'"
        class="px-4 py-2">
      Description
    </button>
    <button
        role="tab"
        id="tab-reviews"
        :aria-selected="active === 'reviews' ? 'true' : 'false'"
        :tabindex="active === 'reviews' ? 0 : -1"
        aria-controls="panel-reviews"
        @click="active = 'reviews'"
        class="px-4 py-2">
      Reviews
    </button>
  </div>

  <div id="panel-description" role="tabpanel" aria-labelledby="tab-description"
       x-show="active === 'description'" tabindex="0">
    <p>Product description...</p>
  </div>
  <div id="panel-reviews" role="tabpanel" aria-labelledby="tab-reviews"
       x-show="active === 'reviews'" tabindex="0" hidden>
    <p>Customer reviews...</p>
  </div>
</div>

7. Keyboard Support and Focus Management for ARIA Widgets

Every ARIA widget pattern defines not only roles and attributes but also a fixed keyboard behavior, precisely prescribed by the WAI-ARIA Authoring Practices. For the tab pattern this means: arrow right and arrow left move to the next or previous tab respectively and simultaneously shift both DOM focus and aria-selected and tabindex. Home jumps to the first tab, End to the last. When a tab is focused via an arrow key, the corresponding focus change must happen in sync with activating the panel, otherwise the visual presentation drifts away from the accessibility tree state.

This logic cannot be achieved declaratively with ARIA attributes alone, it requires complete JavaScript focus management: setting focus deliberately via element.focus(), consistently maintaining the roving tabindex pattern, and cleanly separating keyboard events from click events. Without this logic, the screen reader announces correct roles and states, but keyboard users still cannot operate the component, proof that ARIA attributes alone are never sufficient for accessibility.


// Roving tabindex pattern for tab navigation with arrow keys
function handleTabKeydown(event, tabs, currentIndex) {
  let newIndex = currentIndex;

  switch (event.key) {
    case 'ArrowRight':
      newIndex = (currentIndex + 1) % tabs.length;
      break;
    case 'ArrowLeft':
      newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
      break;
    case 'Home':
      newIndex = 0;
      break;
    case 'End':
      newIndex = tabs.length - 1;
      break;
    default:
      return; // pass through other keys unchanged
  }

  event.preventDefault();

  // Remove the old tab from the tab order
  tabs[currentIndex].setAttribute('tabindex', '-1');

  // Activate the new tab, set focus and aria-selected in sync
  tabs[newIndex].setAttribute('tabindex', '0');
  tabs[newIndex].setAttribute('aria-selected', 'true');
  tabs[currentIndex].setAttribute('aria-selected', 'false');
  tabs[newIndex].focus();
}

8. Testing and Debugging ARIA Implementations

Automated tools like axe-core, Lighthouse, or WAVE reliably find structural ARIA errors: missing labels, incorrect role-attribute combinations, duplicate IDs on aria-labelledby, or insufficient contrast. What they do not detect is whether a widget actually feels correct when operated with the keyboard alone, or whether a live region is actually understandable when read out by a real screen reader. According to common studies, automated tests cover roughly 30 to 40 percent of all accessibility issues, the rest requires manual testing.

The Chrome DevTools Accessibility tab shows the computed accessibility tree directly next to the DOM and immediately reveals which name, role, and states an element actually reports to assistive technologies. For manual testing, at least one pass with a real screen reader belongs in the mandatory checklist: NVDA with Firefox on Windows, VoiceOver with Safari on macOS, or TalkBack on Android. Pure keyboard navigation without a mouse, going consistently from top to bottom through every page, additionally reveals focus traps and missing focus indicators that no automated test finds.


{
  "violations": [
    {
      "id": "aria-required-attr",
      "impact": "critical",
      "description": "Ensures elements with ARIA roles have all required attributes",
      "help": "Required ARIA attributes must be provided",
      "nodes": [
        {
          "target": [".product-tab[role='tab']"],
          "failureSummary": "Fix: Element has role \"tab\" but is missing required aria-selected attribute"
        }
      ]
    },
    {
      "id": "aria-hidden-focus",
      "impact": "serious",
      "description": "Ensures aria-hidden elements do not contain focusable elements",
      "nodes": [
        {
          "target": [".modal-close[aria-hidden='true']"],
          "failureSummary": "Fix: Focusable content should not be aria-hidden"
        }
      ]
    }
  ]
}

9. ARIA Patterns Compared Side by Side

Many everyday interface building blocks can be implemented with ARIA in different ways, with substantial differences in accessibility and maintenance effort. The following overview shows, for five typical tasks, which pattern is actually accessible.

Task Unsafe / Wrong Recommended ARIA Pattern Benefit
Clickable button <div onclick> without a role Use native <button> Keyboard, focus, announcement automatic
Expandable section <div class="accordion"> without status Keep aria-expanded in sync Screen reader knows the true state
Decorative icon <svg> without marking aria-hidden="true" on the icon No pointless reading out of icon names
Showing form errors Error text only marked in red role="alert" + aria-describedby Error is announced immediately
Tab navigation role="tab" without a keyboard handler Complete pattern with arrow keys Widget is actually operable

The common thread in the table: ARIA attributes alone solve nothing when keyboard behavior and state synchronization are missing. A role="tab" without working arrow key navigation only fakes accessibility, and for screen reader users it is just as unusable as a <div onclick> with no ARIA markup at all. Anyone who uses the table as a checklist and implements every pattern completely, rather than partially, avoids the most common ARIA mistakes from the outset.

Mironsoft

ARIA audits, accessibility, and Hyvä accessibility for Magento stores

Use ARIA correctly instead of just sprinkling attributes?

We audit existing components for incorrect ARIA use, replace unnecessary roles with native HTML elements, and rebuild widgets like tabs, accordions, and dialogs to be fully keyboard operable according to the WAI-ARIA Authoring Practices.

ARIA Audit

axe-core analysis and manual review for incorrectly used roles and states

Widget Refactoring

Implement tabs, accordions, and dialogs per the ARIA Authoring Practices

Screen Reader Testing

Anchor manual tests with NVDA, VoiceOver, and keyboard in the QA pipeline

10. Summary

ARIA solves exactly one problem: the gap between visually complex interfaces and what the accessibility tree describes to assistive technologies. ARIA changes neither appearance nor behavior, only roles, states, and properties in the accessibility tree. The most important rule remains to prefer native elements whenever they already provide the required semantics. Only when no native equivalent exists, for example with tabs, comboboxes, or sliders, does the matching ARIA widget pattern come into play, complete with full keyboard support and focus management.

The biggest lever is to never treat ARIA attributes in isolation. A role without a synchronously maintained status, a widget without keyboard support, or an aria-hidden on a focusable element create the appearance of accessibility rather than actual accessibility. Automated tools like axe-core catch only part of these errors, manual testing with a keyboard and a real screen reader remains indispensable for ARIA widgets.

ARIA Fundamentals: When to Use It and When Not To: The Key Takeaways

Rule 1 first

Use a native HTML element when it already provides the required semantics. No role="button" on a <div> when <button> will do.

Role categories

Distinguish landmark, widget, document structure, and live region roles. Widget roles always require full keyboard behavior.

Keep states in sync

Update aria-expanded, aria-selected, and similar attributes via JavaScript on every state change, otherwise the announcement drifts.

Test & verify

axe-core for structural errors, real screen reader tests with NVDA/VoiceOver for actual operability.

11. FAQ: ARIA Fundamentals

1What is ARIA and what is it used for?
A vocabulary of HTML attributes that describes or overrides the accessibility tree. Changes neither appearance nor behavior, only the information for screen readers and assistive technologies.
2What is the most important rule when using ARIA?
Use a native HTML element when it already provides the required semantics, instead of recreating it with ARIA. A button always beats a div with role=button.
3Why prefer native elements over ARIA reimplementations?
Native elements provide keyboard operation, focus styles, and state management automatically. A reimplementation requires all of it manually via JavaScript and is more error prone.
4Difference between ARIA states and properties?
States like aria-expanded change at runtime and must be kept in sync. Properties like aria-label mostly describe stable characteristics.
5When is aria-hidden dangerous?
On focusable elements. It removes from the accessibility tree without hiding visually, an element that is visible but focusable becomes invisible to screen readers.
6aria-live=polite vs. assertive?
polite waits for a pause in speech, assertive interrupts immediately. Reserve assertive only for genuinely urgent messages such as form errors.
7What role categories exist in ARIA?
Landmark, widget, document structure, and live region. Widget roles always require full keyboard behavior implemented via JavaScript.
8How does the roving tabindex pattern work?
Only the active element has tabindex=0, all others -1. Arrow keys update focus and tabindex values via JavaScript in sync.
9Is axe-core & co. enough for testing?
No, it covers only about 30 to 40 percent of all issues. Manual testing with a keyboard and a real screen reader remains necessary.
10What does "No ARIA is better than Bad ARIA" mean?
Incorrect ARIA can make an element completely unusable for screen readers. Without any ARIA at all, at least the native browser semantics still apply.