ARIA widget roles required attributes
Last updated:
Related Guides
ARIA widget roles must include required states and properties
Any element using an ARIA widget role must expose all required states and properties for that role.
This commonly affects custom controls such as switches, sliders, tabs, menus, and progress indicators.
Missing attributes prevent assistive technologies from announcing purpose and state, blocking many users from understanding or operating the control.
Why It Matters
Screen reader users depend on programmatic state to know whether a control is on, off, selected, expanded, or its current value.
When required attributes are absent or out of sync, controls appear inert or misleading to AT, causing errors and abandonment.
This aligns with WCAG 2.2 Success Criterion 4.1.2 (Name, Role, Value): roles, states, and values must be determinable and updated as the UI changes.
Common Causes
- Adding a widget role but omitting its required state (e.g., switch without
aria-checked). - Building range widgets (slider, spinbutton) without
aria-valuemin,aria-valuemax, andaria-valuenow. - Creating a combobox or disclosure pattern without managing
aria-expandedandaria-controlscorrectly. - Using tabs without indicating the active tab via
aria-selected. - Putting state on the wrong element (e.g.,
aria-expandedon the panel instead of the trigger). - Broken ID references for
aria-labelledby,aria-describedby, oraria-controls.
How to Fix
- Prefer native HTML controls first.
- Native inputs (button, checkbox, radio, range, details/summary, select) expose roles and states without ARIA.
- Audit all elements with role attributes.
- For each role, consult WAI-ARIA (1.2) Required States and Properties for what is required vs. allowed.
- Add required states and properties with valid values.
- checkbox, switch, radio, menuitemcheckbox, menuitemradio: set
aria-checked(true/false; checkbox may use mixed). - slider, spinbutton, scrollbar: set
aria-valuemin,aria-valuemax,aria-valuenow; optionallyaria-valuetextfor human-friendly value. - progressbar: set
aria-valuenowunless indeterminate;aria-valuemin(default 0) andaria-valuemax(default 100) may be provided to adjust range. - tab: set
aria-selectedon the active tab; ensure tabpanel hasaria-labelledbyreferencing its tab. - combobox: manage
aria-expandedon the textbox/combobox; usearia-controlsto reference the popup widget; keep states in sync.
- checkbox, switch, radio, menuitemcheckbox, menuitemradio: set
- Keep state in sync on interaction.
- Update ARIA attributes whenever the visual state changes (click, key press, pointer, or touch).
- Validate relationships.
- Ensure IDs referenced by
aria-labelledby,aria-describedby, andaria-controlsexist and are unique.
- Ensure IDs referenced by
- Remove invalid or conflicting ARIA.
- Do not add unsupported states for a role; do not duplicate native semantics.
- Verify keyboard behavior matches the role.
- Follow ARIA Authoring Practices for expected keys (Space/Enter to activate; Arrow keys for sliders, radios, tabs, etc.).
How to Test
- Visual/code inspection
- Locate any element with a role. Confirm required ARIA states/properties for that role are present and have valid values.
- Check that any ID references resolve to existing elements.
- Keyboard check
- Operate each widget using only the keyboard. Confirm state changes update the corresponding ARIA attribute immediately.
- Screen reader check
- With NVDA or JAWS (Windows) or VoiceOver (macOS/iOS), navigate to the control.
- Verify the role is announced correctly (e.g., switch, slider) and the current state/value is spoken and updates on interaction.
- Mobile/touch check
- Activate the control by touch and with a hardware keyboard (if available). Confirm states update and are announced by TalkBack/VoiceOver.
- Automated tools
- Run a11y linters (axe, Accessibility Insights, WAVE) to flag missing required ARIA attributes; verify manually afterward.
Good Example
A custom switch using role="switch" with required state and synchronized updates.
<label id="email-label">Email notifications</label>
<button id="email-switch"
role="switch"
aria-labelledby="email-label"
aria-checked="false"
class="switch-btn">Off</button>
<script>
const sw = document.getElementById('email-switch');
sw.addEventListener('click', () => {
const on = sw.getAttribute('aria-checked') === 'true';
sw.setAttribute('aria-checked', String(!on));
sw.textContent = on ? 'Off' : 'On';
});
// Button natively supports Space/Enter activation
</script>Bad Example
A custom switch without the required aria-checked, leaving AT with no state to announce.
<div class="switch-btn" role="switch">On</div>
<!-- No aria-checked; screen readers cannot determine on/off state -->Quick Checklist
- Every ARIA widget role includes all required states/properties for that role.
- States update instantly on interaction (keyboard, mouse, touch).
- Range widgets expose
aria-valuemin,aria-valuemax, andaria-valuenowwith valid values. - Selection and disclosure states are represented (
aria-selected,aria-expanded) on the correct element. - ID references for aria-labelledby/aria-controls/aria-describedby exist and are unique.
- No unsupported or redundant ARIA is applied; prefer native elements when possible.
- Keyboard operation matches the expected pattern; screen readers announce role and state accurately.