ADA Compliance Professionals

    ARIA widget roles required attributes

    Last updated:

    Who it helps:
    Blind
    Standard:
    WCAG 2.2 Level A

    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, and aria-valuenow.
    • Creating a combobox or disclosure pattern without managing aria-expanded and aria-controls correctly.
    • Using tabs without indicating the active tab via aria-selected.
    • Putting state on the wrong element (e.g., aria-expanded on the panel instead of the trigger).
    • Broken ID references for aria-labelledby, aria-describedby, or aria-controls.

    How to Fix

    1. Prefer native HTML controls first.
      • Native inputs (button, checkbox, radio, range, details/summary, select) expose roles and states without ARIA.
    2. Audit all elements with role attributes.
      • For each role, consult WAI-ARIA (1.2) Required States and Properties for what is required vs. allowed.
    3. 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; optionally aria-valuetext for human-friendly value.
      • progressbar: set aria-valuenow unless indeterminate; aria-valuemin (default 0) and aria-valuemax (default 100) may be provided to adjust range.
      • tab: set aria-selected on the active tab; ensure tabpanel has aria-labelledby referencing its tab.
      • combobox: manage aria-expanded on the textbox/combobox; use aria-controls to reference the popup widget; keep states in sync.
    4. Keep state in sync on interaction.
      • Update ARIA attributes whenever the visual state changes (click, key press, pointer, or touch).
    5. Validate relationships.
      • Ensure IDs referenced by aria-labelledby, aria-describedby, and aria-controls exist and are unique.
    6. Remove invalid or conflicting ARIA.
      • Do not add unsupported states for a role; do not duplicate native semantics.
    7. 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.

    HTML
    <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.

    HTML
    <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, and aria-valuenow with 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.