ARIA Usage
Principle
Use ARIA only when native HTML cannot express the required semantics or behavior. Incorrect ARIA is worse than no ARIA.
WCAG Reference
Avoid Replacing Native Semantics
Guidance
- ARIA must not be used to replace native HTML semantics. Always use the correct HTML element first, and add ARIA only when no native element or attribute can provide the required meaning or behavior.
- Examples:
- Use
<button>, not<div role="button"> - Do not add
role="navigation"on a<nav> - Do not add
role="link"on a<button> - Example: do not use ARIA list roles (
role="list",role="listitem") instead of native<ul>/<li>
- Use
- Examples:
- Do not add
aria-labelwhen the visible text alone provides the full accessible name. - Use
aria-labelonly when additional context is needed for non-visual users — for example, repeated action buttons inside product cards (“Add to cart: Product Name”). - Avoid ARIA attributes that conflict with native behavior or override built-in semantics.
- When ARIA is required (e.g., for custom widgets), ensure that name, role, state and value are properly exposed to assistive technologies and dynamically updated via JavaScript.
- If you find yourself adding many ARIA attributes, consider whether the HTML structure is correct.
Correct Use of ARIA Roles
Guidance
- Use ARIA roles only when building custom components that have no native HTML equivalent.
- If a component behaves like a standard HTML element, use the native element instead of adding a role.
Roles that are appropriate for custom widgets
- These roles are valid only when the component fully implements the required keyboard behavior and ARIA states.
role="dialog"— for custom modal windows (must trap focus and restore it on close).role="tablist", role="tab", role="tabpanel" — for custom tab components using roving tabindex.role="listbox", role="option" — for fully custom select-like controls (not for autocomplete menus unless following the pattern).role="switch"— only if implementing switch behavior (not just for styled checkboxes).role="progressbar"— for custom progress indicators that expose value via ARIA.
Roles that must be used with extreme caution
- These are commonly misused and are appropriate only in specialized application-like UIs, not in standard e-commerce interfaces.
role="menu"/role="menuitem"- These are not for site navigation, dropdowns, or category menus.
- They represent application menus, like those in desktop software, with very specific keyboard rules (arrows only, no Tab).
role="button"- Use only on custom, non-native interactive elements that genuinely cannot be
<button>. (This should be extremely rare.)
- Use only on custom, non-native interactive elements that genuinely cannot be
Roles that should almost never be added manually
- These roles almost always duplicate native semantics and should not be used unless absolutely required:
role="navigation"-><nav>role="heading"-><h1>–<h6>role="main"-><main>role="form"-><form>role="list"/role="listitem"-><ul>/<li>
General rule
- If you add a role, you take full responsibility for the expected keyboard behavior and ARIA states (name, role, value). If you cannot implement all necessary interactions, do not use the role.
ARIA Attributes Must Reflect State
Guidance
- ARIA attributes must always match the current state of the component — if the UI changes, ARIA must change too.
- Update state attributes dynamically:
aria-expanded="true|false"for expandable elements (accordions, menus, dropdowns).aria-selected="true|false"for selected tabs or options in listbox-like components.aria-hidden="true|false"when toggling visibility of UI regions that should or should not be exposed to assistive technologies.
- Never leave outdated ARIA values — stale attributes cause screen readers to announce the wrong state or mislead users.
- If the visual state and ARIA state disagree, the component is not accessible, even if it looks correct visually.
Labelling and Describing Elements
Guidance
- Prefer native HTML labeling mechanisms (e.g.
<label for>) and visible text labels whenever possible. They create the clearest and most reliable accessible names. - When an element has no visible label:
- Use
aria-labelfor short, simple labels on icon buttons or compact controls. - Use
aria-labelledbyto reference an existing visible label elsewhere in the DOM. - Use
aria-describedbyto associate helper text, additional details, or error messages.
- Use
- Do not duplicate the same words in both visible text and aria-label — screen readers will read them twice or override the visible name.
- For actions repeated in lists (e.g., multiple “Add to cart” buttons), provide extra context only in the accessible name, not visually:
- Example: aria-label="Add to cart: Hammer Drill".
Avoid ARIA for Styling or Scripting
Guidance
- ARIA does not affect appearance — never use ARIA attributes as selectors for styling or JavaScript logic.
- Do not use ARIA as a hack to fix structural or semantic issues — correct the HTML instead.
- Avoid adding ARIA just to detect “states” for JavaScript; use classes or data attributes for that purpose.
- ARIA exists solely to communicate meaning and state to assistive technologies, not to control layout, behavior, or visuals.
ARIA and Visibility
Guidance
- When an element should be hidden from assistive technologies, use
aria-hidden="true"and ensure it is not focusable. - Never use
aria-hidden="true"on interactive or focusable elements — screen readers will ignore them, but keyboard users will still land on them. - CSS-only hiding (opacity, visibility: hidden, off-screen positioning) does not hide content from screen readers. Pair visibility changes with proper ARIA (or inert, if supported).
- Be careful when hiding parents with
display: noneorvisibility: hidden— ensure that interactive descendants are removed from the tab order, and ARIA state reflects the visual state. - When toggling visibility, keep ARIA, focusability and keyboard behavior in sync to avoid exposing hidden or inactive content.
Announcing Dynamic Content (ARIA Live Regions)
Principle
When the UI changes dynamically (loading, errors, updates), screen reader users must be informed without losing focus or navigation context.
Guidance
- Use
aria-liveregions to announce important updates that happen without a page reload.aria-live="polite"— announce non-urgent changes (e.g., “5 items loaded”).aria-live="assertive"— announce critical messages (e.g., errors). Use sparingly.
- Live regions should already exist in the DOM before updates occur. Screen readers are more reliable at announcing changes to existing live regions than newly inserted ones.
- Use
aria-busy="true"on containers that are loading new content; switch tofalsewhen loading completes. - For inline errors:
- Use
aria-describedbyoraria-errormessageto associate the error text with the field. - Update the error text inside an aria-live region for immediate announcement.
- Use
- Do not use
aria-liveto announce visual changes that are not meaningful (“button color changed”). - Do not announce too frequently — excessive messages overwhelm users.
- Dynamic announcements must never steal focus unless absolutely necessary.