Invalid ARIA attribute value (aria-valid-attr-value)

The aria-valid-attr-value rule flags ARIA attributes whose value is not one the WAI-ARIA specification allows – aria-expanded="yes", aria-valuenow="50%", or an aria-controls that points to an id nobody has. A browser cannot pass on a state it does not understand. The fix is to use the exact value the attribute defines and to point references at ids that exist.

What the rule means

Every ARIA attribute has a value type in WAI-ARIA 1.2 [3]. Some take true or false (aria-disabled), some take true, false or mixed (aria-checked), some a fixed list of tokens (aria-haspopup: menu, listbox, tree, grid, dialog), some a number (aria-valuenow, aria-level), and some one or more ids of other elements (aria-controls, aria-labelledby). Anything outside the defined set is invalid, and the browser either ignores it or falls back to a default.

axe checks every aria-* attribute on every element. It fails a value that is not in the set – including a reference such as aria-controls or aria-owns to an id that does not exist on the page, as long as the element is expanded. It asks a person instead of failing in a few cases:

  • aria-labelledby or aria-describedby pointing only to ids that are not on the page – the target may be added later;
  • aria-current with a value outside page, step, location, date, time, true, false;
  • an empty value on an attribute that is not free text;
  • aria-controls on an element with aria-haspopup, whose popup may not exist until it opens.

The rule does not check whether a valid value is true: aria-expanded="false" on a menu that is visibly open passes.

Who is affected

People using a screen reader hear the state the markup announces – "collapsed", "checked", "3 of 5" – and nothing else. If the value is invalid they hear nothing or a wrong default, and have to guess whether a panel opened. Voice control users who say "click Shipping" do not learn that anything changed either.

Why the check fails

  • Words instead of tokens: aria-expanded="yes", aria-hidden="hidden", aria-selected="selected" copied from class names.
  • Template output: a framework writes aria-expanded="${isOpen}" literally, or aria-checked="null" when the state was never set.
  • Units in numbers: aria-valuenow="50%" or aria-level="h3".
  • Typos in ids: aria-controls="panel-shiping" against id="panel-shipping".
  • Components rendered without their target: a disclosure button kept while the panel it controls was removed by the CMS.
  • Old ARIA habits: aria-haspopup="yes" or aria-live="on", which never were valid values.

How to fix it

  1. Look up the attribute's allowed values in WAI-ARIA 1.2 [3] – it is a short list for most of them.
  2. Fix the value in the component, not on the page: one accordion template usually produces every occurrence.
  3. Let the script that toggles the panel also write the state, so it stays correct.
<!-- Before: "yes" is not a value aria-expanded knows, and the id has a typo -->
<div class="accordion">
  <h3 class="accordion__heading">
    <button class="accordion__trigger" aria-expanded="yes" aria-controls="panel-shiping">
      Shipping and returns
    </button>
  </h3>
  <div class="accordion__panel" id="panel-shipping">
    <p>Free returns within 30 days.</p>
  </div>
</div>
<!-- After: a real token, and a reference to an id that exists -->
<div class="accordion">
  <h3 class="accordion__heading">
    <button class="accordion__trigger" aria-expanded="true" aria-controls="panel-shipping">
      Shipping and returns
    </button>
  </h3>
  <div class="accordion__panel" id="panel-shipping">
    <p>Free returns within 30 days.</p>
  </div>
</div>

A valid value is only half the job. The state has to follow the panel:

// Write the state in the same place that opens and closes the panel
trigger.addEventListener('click', () => {
  const open = trigger.getAttribute('aria-expanded') === 'true'
  trigger.setAttribute('aria-expanded', String(!open)) // always "true" or "false"
  panel.hidden = open
})

How to test it manually

  1. Open the element in your browser's developer tools and look at the Accessibility pane (Chrome, Edge, Firefox): it shows the role, name and the states the browser actually understood.
  2. Operate the widget – open the accordion, pick a tab, move the slider – and watch the state change in that pane.
  3. With a screen reader (NVDA is free, VoiceOver is built into macOS and iOS), check that "expanded" and "collapsed" match what you see.
  4. What the rule cannot see: a valid state that is wrong, a state that never updates, and a value on the wrong element.

4.1.2 Name, Role, Value, Level A: states and properties of custom controls must be available to assistive technology. A reference to a missing id can also break a relationship covered by 1.3.1 Info and Relationships.

How Reviseberg reports it

Reviseberg runs aria-valid-attr-value on every crawled page. The issue list shows the rule with its severity, WCAG 4.1.2 and Level A, the number of elements and pages, and the points a fix gets you back; the detail view shows the selector, the HTML snippet with the invalid value and every affected page. Because the value usually comes from one component, one fix typically clears many pages.

The cases axe leaves open – references to ids that are not there yet, unusual aria-current values, empty values – go to the Potential issues queue for a person to decide; they cost nothing and are never counted as a pass. Findings can be marked "ignore", "can't fix" or "false positive" with a reason, and every decision is logged. A clean run does not settle 4.1.2: whether each state is right is still a question for a person.

  • undefined – a required state such as aria-checked is missing altogether.
  • undefined – the attribute is valid but not supported on this role.
  • undefined – a button left without a name, for example when its aria-labelledby points nowhere.

Find invalid ARIA across your site – get a free scan

Frequently asked questions

Is aria-expanded="yes" really a failure? Screen readers might cope.

It is invalid under WAI-ARIA 1.2, which allows only true, false and undefined. Browsers do not guess, so the button is not announced as expanded.

Why is an aria-describedby to a missing id only a potential issue?

Because the target may be inserted later, for example an error message that appears after submitting. axe cannot know, so it asks a person instead of failing the page.

Does aria-hidden="False" with a capital F count?

Tokens are compared case-insensitively, so it is accepted. Write lower case anyway – it is what the specification uses.

Does the rule check whether the state is correct?

No. It checks the value's form. aria-expanded="false" on an open menu passes; only operating the widget shows that it is wrong.

Sources

  1. W3C, Understanding SC 4.1.2 Name, Role, Value – https://www.w3.org/WAI/WCAG22/Understanding/name-role-value
  2. Deque University, axe-core 4.13 rule aria-valid-attr-value – https://dequeuniversity.com/rules/axe/4.13/aria-valid-attr-value
  3. W3C, Accessible Rich Internet Applications (WAI-ARIA) 1.2 – https://www.w3.org/TR/wai-aria-1.2/
  4. W3C, ACT rule: ARIA state or property has valid value – https://www.w3.org/WAI/standards-guidelines/act/rules/6a7281/
  5. W3C, ARIA Authoring Practices Guide: Accordion pattern – https://www.w3.org/WAI/ARIA/apg/patterns/accordion/

See what a scan finds on your site

One page in about thirty seconds, no email needed. The full report covers up to 100 pages and a keyboard journey.