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-labelledbyoraria-describedbypointing only to ids that are not on the page – the target may be added later;aria-currentwith a value outsidepage,step,location,date,time,true,false;- an empty value on an attribute that is not free text;
aria-controlson an element witharia-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, oraria-checked="null"when the state was never set. - Units in numbers:
aria-valuenow="50%"oraria-level="h3". - Typos in ids:
aria-controls="panel-shiping"againstid="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"oraria-live="on", which never were valid values.
How to fix it
- Look up the attribute's allowed values in WAI-ARIA 1.2 [3] – it is a short list for most of them.
- Fix the value in the component, not on the page: one accordion template usually produces every occurrence.
- 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
- 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.
- Operate the widget – open the accordion, pick a tab, move the slider – and watch the state change in that pane.
- With a screen reader (NVDA is free, VoiceOver is built into macOS and iOS), check that "expanded" and "collapsed" match what you see.
- What the rule cannot see: a valid state that is wrong, a state that never updates, and a value on the wrong element.
Related WCAG criterion
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.