ARIA roles must conform to valid values
An ARIA attribute has an invalid value. Screen readers and assistive technologies cannot interpret invalid ARIA values, breaking accessibility features.
Rule Description
This rule ensures all ARIA attribute values conform to their defined value types and ranges according to the ARIA specification.
What This Rule Checks
- Boolean attributes have
"true"or"false"values - ID reference attributes point to existing elements
- Token attributes use valid predefined values
- Numeric attributes are within valid ranges
- Token lists contain only valid tokens
What This Rule Does Not Check
- Whether ARIA attributes are appropriate for the element (different rule)
- Whether required ARIA attributes are present (different rule)
- The semantic correctness of ARIA usage
Best Practices
- Use strings for booleans -
"true"/"false", nottrue/false - Verify ID references - Ensure referenced elements exist
- Check specifications - Look up valid values for each attribute
- Use correct types - Numbers for numeric, strings for tokens
- Test with validators - Use HTML/ARIA validators
Why It Matters
Impact on Users
- Screen reader users receive incorrect or no information
- Assistive technologies may malfunction or ignore elements
- Keyboard users may lose access to interactive elements
- All users relying on AT may have broken experiences
Real-World Scenario
A button has aria-pressed="active" instead of aria-pressed="true". Screen readers don't announce the pressed state, so users don't know if the button is active or not. They may press it multiple times or get confused about the UI state.
How to Fix
Solution 1: Use Correct Boolean Values
ARIA boolean attributes must be "true" or "false" (as strings).
Bad Examples:
<!-- FAIL - Invalid boolean values --> <button aria-pressed="yes">Toggle</button> <button aria-expanded="1">Menu</button> <button aria-checked="on">Checkbox</button> <div aria-hidden="true false">Content</div>
Good Examples:
<!-- PASS - Valid boolean values --> <button aria-pressed="true">Toggle</button> <button aria-expanded="false">Menu</button> <button aria-checked="true">Checkbox</button> <div aria-hidden="true">Content</div>
Solution 2: Valid ID References
Attributes like aria-labelledby and aria-describedby must reference existing IDs.
Bad Examples:
<!-- FAIL - Referenced IDs don't exist --> <button aria-labelledby="non-existent-id">Click</button> <input aria-describedby="missing-hint" type="text">
Good Examples:
<!-- PASS - Valid ID references --> <span id="button-label">Save Changes</span> <button aria-labelledby="button-label"> <i class="icon-save"></i> </button> <label for="email">Email:</label> <input type="email" id="email" aria-describedby="email-hint"> <span id="email-hint">We'll never share your email</span>
Solution 3: Valid Token Values
Some attributes accept specific predefined tokens.
aria-live Values:
<!-- FAIL --> <div aria-live="medium">Updates</div> <div aria-live="yes">Notifications</div> <!-- PASS - Valid values: off, polite, assertive --> <div aria-live="polite">Updates</div> <div aria-live="assertive">Error messages</div> <div aria-live="off">Static content</div>
aria-current Values:
<!-- FAIL --> <a href="/about" aria-current="yes">About</a> <li aria-current="active">Item 1</li> <!-- PASS - Valid values: page, step, location, date, time, true, false --> <a href="/about" aria-current="page">About</a> <li aria-current="step">Step 1</li> <a href="/location" aria-current="location">New York</a> <time aria-current="date">Today</time>
aria-autocomplete Values:
<!-- FAIL --> <input aria-autocomplete="on" type="text"> <input aria-autocomplete="enabled" type="text"> <!-- PASS - Valid values: inline, list, both, none --> <input aria-autocomplete="list" type="text"> <input aria-autocomplete="both" type="text">
Solution 4: Valid Number Ranges
Numeric attributes must be within valid ranges.
aria-valuemin, aria-valuemax, aria-valuenow:
<!-- FAIL - valuenow outside min/max range --> <div role="slider" aria-valuemin="0" aria-valuemax="100" aria-valuenow="150"> </div> <!-- PASS - valuenow within range --> <div role="slider" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"> </div>
aria-level (must be positive integer):
<!-- FAIL --> <div role="heading" aria-level="0">Title</div> <div role="heading" aria-level="-1">Title</div> <div role="heading" aria-level="2.5">Title</div> <!-- PASS --> <div role="heading" aria-level="1">Title</div> <div role="heading" aria-level="2">Subtitle</div>
Solution 5: Valid Token Lists
Some attributes accept space-separated lists of valid tokens.
aria-dropeffect Values:
<!-- FAIL --> <div aria-dropeffect="drag">Drop zone</div> <!-- PASS - Valid values: copy, execute, link, move, none, popup --> <div aria-dropeffect="copy move">Drop zone</div> <div aria-dropeffect="link">Drop zone</div>
aria-relevant Values:
<!-- FAIL --> <div aria-live="polite" aria-relevant="everything">Updates</div> <!-- PASS - Valid values: additions, removals, text, all --> <div aria-live="polite" aria-relevant="additions text">Updates</div> <div aria-live="polite" aria-relevant="all">Updates</div>
Common Mistakes
1. Boolean Values as Actual Booleans
// FAIL - JavaScript boolean, needs string element.setAttribute('aria-expanded', true); element.setAttribute('aria-pressed', false); // PASS - String values element.setAttribute('aria-expanded', 'true'); element.setAttribute('aria-pressed', 'false');
2. Invalid ID References
<!-- FAIL - ID doesn't exist --> <button aria-labelledby="save-label">Save</button> <!-- PASS - ID exists --> <span id="save-label">Save Changes</span> <button aria-labelledby="save-label">Save</button>
3. Wrong Token Values
<!-- FAIL --> <div aria-live="immediate">Alert</div> <div role="button" aria-pressed="on">Toggle</div> <nav aria-current="active">About</nav> <!-- PASS --> <div aria-live="assertive">Alert</div> <div role="button" aria-pressed="true">Toggle</div> <nav aria-current="page">About</nav>
4. Numbers as Strings vs Actual Numbers
<!-- Both work, but string is more explicit --> <div role="slider" aria-valuenow="50"></div> <div role="slider" aria-valuenow=50></div> <!-- FAIL - Invalid number --> <div role="slider" aria-valuenow="fifty"></div>
5. Case Sensitivity
<!-- FAIL - Values are case-sensitive --> <button aria-pressed="True">Toggle</button> <div aria-live="Polite">Updates</div> <!-- PASS - Lowercase --> <button aria-pressed="true">Toggle</button> <div aria-live="polite">Updates</div>
Attribute Reference
Boolean Attributes
Values: "true" or "false"
aria-atomic="true" aria-busy="false" aria-checked="true" aria-disabled="false" aria-expanded="true" aria-grabbed="false" aria-haspopup="true" aria-hidden="false" aria-invalid="true" aria-modal="false" aria-multiline="true" aria-multiselectable="false" aria-pressed="true" aria-readonly="false" aria-required="true" aria-selected="false"
Tristate Attributes
Values: "true", "false", or "mixed"
aria-checked="mixed" aria-pressed="mixed"
ID Reference Attributes
Values: Valid element ID
aria-labelledby="label-id" aria-describedby="desc-id help-id" aria-controls="panel-id" aria-owns="child1 child2" aria-activedescendant="option-3" aria-errormessage="error-msg"
Token Attributes
aria-autocomplete:
Values: none, inline, list, both <input aria-autocomplete="list">
aria-current:
Values: false, true, page, step, location, date, time <a aria-current="page">Home</a>
aria-dropeffect:
Values: none, copy, execute, link, move, popup <div aria-dropeffect="copy move">Drop</div>
aria-haspopup:
Values: false, true, menu, listbox, tree, grid, dialog <button aria-haspopup="menu">Options</button>
aria-invalid:
Values: false, true, grammar, spelling <input aria-invalid="spelling">
aria-live:
Values: off, polite, assertive <div aria-live="polite">Status</div>
aria-orientation:
Values: horizontal, vertical, undefined <div role="slider" aria-orientation="horizontal">
aria-relevant:
Values: additions, removals, text, all <div aria-relevant="additions text">
aria-sort:
Values: none, ascending, descending, other <th aria-sort="ascending">Name</th>
Numeric Attributes
aria-colcount="10" aria-colindex="3" aria-colspan="2" aria-level="2" aria-posinset="3" aria-rowcount="100" aria-rowindex="5" aria-rowspan="2" aria-setsize="10" aria-valuemax="100" aria-valuemin="0" aria-valuenow="50"
String Attributes
aria-label="Search" aria-placeholder="Enter email" aria-roledescription="slide" aria-valuetext="Medium priority"
Complete Examples
Toggle Button
<button type="button" aria-pressed="false" onclick="togglePressed(this)"> Mute Audio </button> <script> function togglePressed(button) { const pressed = button.getAttribute('aria-pressed') === 'true'; button.setAttribute('aria-pressed', String(!pressed)); } </script>
Expandable Menu
<button aria-expanded="false" aria-controls="menu-panel" aria-haspopup="true" onclick="toggleMenu()"> Menu </button> <div id="menu-panel" hidden> <a href="/home">Home</a> <a href="/about">About</a> </div> <script> function toggleMenu() { const button = document.querySelector('[aria-expanded]'); const panel = document.getElementById('menu-panel'); const expanded = button.getAttribute('aria-expanded') === 'true'; button.setAttribute('aria-expanded', String(!expanded)); panel.hidden = expanded; } </script>
Slider
<div role="slider" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50" aria-valuetext="50%" aria-label="Volume" tabindex="0"> </div> <script> const slider = document.querySelector('[role="slider"]'); slider.addEventListener('keydown', (e) => { let value = parseInt(slider.getAttribute('aria-valuenow')); if (e.key === 'ArrowRight') value = Math.min(100, value + 10); if (e.key === 'ArrowLeft') value = Math.max(0, value - 10); slider.setAttribute('aria-valuenow', String(value)); slider.setAttribute('aria-valuetext', `${value}%`); }); </script>
Live Region
<div role="status" aria-live="polite" aria-atomic="true" aria-relevant="additions text"> <p id="status-message"></p> </div> <script> function updateStatus(message) { document.getElementById('status-message').textContent = message; } </script>
Testing
Manual Testing
- Inspect elements with ARIA attributes
- Verify values match specifications
- Check ID references point to existing elements
- Test boolean attributes are strings
- Validate numeric ranges
Screen Reader Testing
NVDA/JAWS: Navigate to elements with ARIA
Expected: Correct states and properties announced
Test button with aria-pressed:
Expected: "Toggle button, pressed" or "Toggle button, not pressed"
Test expandable section:
Expected: "Menu, collapsed" or "Menu, expanded"
Automated Testing
// Check aria-pressed values document.querySelectorAll('[aria-pressed]').forEach(el => { const value = el.getAttribute('aria-pressed'); if (!['true', 'false', 'mixed'].includes(value)) { console.error('Invalid aria-pressed value:', value, el); } }); // Check ID references exist document.querySelectorAll('[aria-labelledby]').forEach(el => { const ids = el.getAttribute('aria-labelledby').split(' '); ids.forEach(id => { if (!document.getElementById(id)) { console.error('aria-labelledby references non-existent ID:', id, el); } }); }); // Using axe-core const results = await axe.run(); const violations = results.violations.filter( v => v.id === 'aria-valid-attr-value' );
Browser DevTools
// Find all ARIA attributes const ariaAttrs = []; document.querySelectorAll('*').forEach(el => { Array.from(el.attributes) .filter(attr => attr.name.startsWith('aria-')) .forEach(attr => ariaAttrs.push({ element: el.tagName, attribute: attr.name, value: attr.value })); }); console.table(ariaAttrs);
External Resources
Automate Your Accessibility Testing
Our tool automatically checks for this rule and hundreds of other accessibility issues.
Start Your Free Trial