WCAG 4.1.2Name, Role, Value

Inaccessible Custom Controls

What is this issue?

Inaccessible custom controls are interactive widgets built with generic HTML elements (div, span) that lack the proper ARIA roles, states, and properties needed for assistive technologies to understand and interact with them. WCAG Success Criterion 4.1.2 (Level A) requires that for all user interface components, the name, role, and value can be programmatically determined, and notification of changes to these items is available to assistive technologies.

The web is filled with custom-built dropdown menus, toggle switches, sliders, date pickers, tab panels, accordions, and autocomplete inputs. When these are built from <div> and <span> elements with JavaScript event handlers, they look and work like their native counterparts for mouse users but are completely invisible or unusable for assistive technology users. A <div onclick="toggle()"> styled to look like a button has no role, no keyboard support, and no state information.

Even when developers add role attributes, they often miss critical states and properties. A custom checkbox with role="checkbox" but without aria-checked is announced as a checkbox but the user cannot determine its current state. A custom combobox without aria-expanded leaves the user guessing whether the dropdown list is open or closed.

Impact on users

For screen reader users, a custom control without proper ARIA is either invisible or confusing. A custom dropdown built with <div> elements is announced as "group" or not announced at all. The user has no idea it is a dropdown, cannot open it with keyboard, and cannot navigate its options. For all practical purposes, the control does not exist.

Keyboard users are equally affected. Native HTML buttons respond to Enter and Space. Native checkboxes toggle with Space. Custom controls built with <div> elements respond to no keyboard events unless the developer explicitly adds them. This creates mouse-only interactions that exclude all keyboard users.

The impact is magnified in single-page applications and component libraries where custom controls are used pervasively. A React or Vue component library that implements inaccessible custom controls propagates the problem to every application that uses it, potentially affecting thousands of sites.

Code example

Before (non-compliant)
<!-- Custom toggle with no accessibility -->
<div class="toggle" onclick="toggle(this)">
  <div class="toggle-thumb"></div>
</div>

<!-- Custom dropdown with no role or keyboard -->
<div class="dropdown" onclick="openMenu()">
  Select an option
  <div class="dropdown-list" style="display:none;">
    <div onclick="select('a')">Option A</div>
    <div onclick="select('b')">Option B</div>
  </div>
</div>

<!-- Custom tab panel -->
<div class="tabs">
  <div class="tab active" onclick="showTab(0)">Tab 1</div>
  <div class="tab" onclick="showTab(1)">Tab 2</div>
</div>
After (compliant)
<!-- Accessible toggle switch -->
<button role="switch" aria-checked="false"
  aria-label="Dark mode"
  onclick="toggle(this)"
  class="toggle">
  <span class="toggle-thumb"></span>
</button>

<!-- Accessible dropdown with ARIA -->
<div class="dropdown">
  <button aria-haspopup="listbox" aria-expanded="false"
    onclick="openMenu()">Select an option</button>
  <ul role="listbox" hidden>
    <li role="option" tabindex="0"
      onclick="select('a')">Option A</li>
    <li role="option" tabindex="-1"
      onclick="select('b')">Option B</li>
  </ul>
</div>

<!-- Accessible tab panel -->
<div role="tablist">
  <button role="tab" aria-selected="true"
    aria-controls="panel-1" id="tab-1">Tab 1</button>
  <button role="tab" aria-selected="false"
    aria-controls="panel-2" id="tab-2">Tab 2</button>
</div>
<div role="tabpanel" id="panel-1"
  aria-labelledby="tab-1">...</div>

How Scrutia detects this issue

Scrutia identifies custom interactive elements (clickable divs, styled spans with event handlers) and checks whether they have appropriate ARIA roles, states, and properties. It verifies that custom controls are keyboard-accessible, have accessible names, and update their ARIA states dynamically. Controls missing role, aria-checked, aria-expanded, or other required attributes are flagged with the correct ARIA pattern to implement.

Check your site for this issue

Scrutia audits your site against WCAG criteria in minutes.

Free audit

Frequently Asked Questions

Should I use native HTML elements instead of custom controls?
Yes, whenever possible. Native <button>, <input type="checkbox">, <select>, and <details> elements come with built-in accessibility, keyboard support, and semantics. Only build custom controls when native elements cannot meet your design requirements, and always implement full ARIA support.
What ARIA attributes are most commonly missed?
aria-expanded (for dropdowns and accordions), aria-checked (for custom checkboxes and toggles), aria-selected (for tabs and listbox options), and aria-label or aria-labelledby (for controls without visible text labels). These states must update dynamically as the user interacts.
How do I make custom controls keyboard accessible?
Add tabindex="0" to make the control focusable, then add keydown event listeners for the expected keys. Buttons need Enter and Space. Dropdowns need Enter, Space, Arrow keys, and Escape. Follow the WAI-ARIA Authoring Practices Guide for keyboard patterns.
Do component libraries handle accessibility automatically?
Some do, some do not. Libraries like Radix UI, Headless UI, and React Aria are built with accessibility as a core feature. Others focus on visual design and leave accessibility to the developer. Always verify that your component library implements proper ARIA patterns.

Does your site have this issue?

Scrutia scans your pages against WCAG success criteria and delivers actionable fixes. Results in 5 minutes.

Run a free audit