Skip to content

ARIA attributes need valid values

  • Rule: axe/aria-valid-attr-value
  • Checks: ARIA attributes must conform to valid values
  • WCAG: 4.1.2
  • Fix class: mechanical: A fix needs no authored text or visual decision.
  • People who use a screen reader or a braille display, most of them blind.

An attribute has a value ARIA does not define, such as aria-expanded="yes", aria-live="true" or aria-haspopup="yes"

Section titled “An attribute has a value ARIA does not define, such as aria-expanded="yes", aria-live="true" or aria-haspopup="yes"”

Use one of the values defined for that attribute: "true" or "false" for aria-expanded, with "mixed" also allowed for aria-checked and aria-pressed; "polite", "assertive" or "off" for aria-live; "false", "true", "menu", "listbox", "tree", "grid" or "dialog" for aria-haspopup. Where script writes a missing value as null or an empty string, write a real value or remove the attribute.

An ID reference points at nothing, such as aria-controls or aria-activedescendant naming a missing id

Section titled “An ID reference points at nothing, such as aria-controls or aria-activedescendant naming a missing id”

Point it at an element that exists. axe allows a missing aria-controls target while aria-expanded="false", for a popup rendered only when open. A missing aria-labelledby or aria-describedby target gets a review instead: check that the name or description it should supply is there.

A number has a unit or is out of range, such as aria-valuenow="50%" or aria-level="0"

Section titled “A number has a unit or is out of range, such as aria-valuenow="50%" or aria-level="0"”

Write numbers as plain numbers, such as aria-valuenow="50" with aria-valuemin and aria-valuemax, and put a formatted value in aria-valuetext. Levels and positions start at 1.

aria-errormessage points at a hidden element, or at a message nothing announces

Section titled “aria-errormessage points at a hidden element, or at a message nothing announces”

Show the message whenever aria-invalid="true", and also list its id in aria-describedby, or make it a live region, so screen readers announce it. Point at a single id.

<label for="email">Email</label>
<input id="email" type="email" aria-invalid="true" aria-errormessage="email-error" aria-describedby="email-error">
<p id="email-error">Enter an email address such as name@example.com.</p>

Writing an unset script variable into the attribute, so it reads aria-expanded="undefined"

Section titled “Writing an unset script variable into the attribute, so it reads aria-expanded="undefined"”

axe accepts it, since undefined is a value ARIA defines, but it means the element does not expand at all, so screen readers announce no state. Write "true" or "false".

Removing the ID reference to silence the rule

Section titled “Removing the ID reference to silence the rule”

The rule passes, but the relationship is gone, such as which popup a combobox controls or which option is active.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

aria-invalid gets an error message or other value, such as aria-invalid={errors.email}

Section titled “aria-invalid gets an error message or other value, such as aria-invalid={errors.email}”

Pass a boolean, aria-invalid={Boolean(error)}, which React writes as "true" or "false", and show the message in an element that aria-describedby points at.

import { useId } from 'react';
function EmailField({ error }) {
const id = useId();
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} type="email" aria-invalid={Boolean(error)} aria-describedby={error ? `${id}-error` : undefined} />
{error && <p id={`${id}-error`}>{error}</p>}
</>
);
}

A boolean is passed to an attribute that takes words, such as aria-live={true}

Section titled “A boolean is passed to an attribute that takes words, such as aria-live={true}”

Pass one of the attribute’s own values, such as aria-live="polite". React writes a boolean as "true" or "false", which only some attributes accept.

aria-activedescendant keeps the id of an option that is no longer rendered

Section titled “aria-activedescendant keeps the id of an option that is no longer rendered”

Derive it from state each render, and set it to undefined when no rendered option is active; React then leaves the attribute out.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.