WCAG 4.1.2 · Level A · ADA Title II

Custom controls and the ADA: name, role, value

Every control must expose a name, role, state and value to assistive technology. See where vendor widgets and custom components fall short.

What assistive technology needs to know

For every user interface component, including form elements, links and components generated by scripts, the name and role must be programmatically determined. States, properties and values that the user can set must be programmatically settable. And changes to those items must be available to user agents, including assistive technologies.

In everyday terms, a screen reader or voice control tool must be able to say "Book appointment, button" or "County, combo box, Alameda, collapsed", and to operate the control and hear what changed. If a control is only a styled box with a click handler, none of that exists.

Native HTML controls used as the specification intends do this for free. The work starts when a control is built from generic elements, or when script changes a control's meaning or state.

Custom widgets on government sites

Date pickers, scheduling grids, address autocompletes and chat launchers are often vendor widgets dropped into a city or county page. Visually they look like controls. In the accessibility tree they are anonymous groups of text. A resident who relies on a screen reader or voice commands cannot book the appointment, even though the page looks complete.

Vendor widgets are a common gap. During procurement, ask for the accessibility tree of a widget, not just a screenshot.

A custom county picker

A set of divs that imitates a select has no role, no value and no keyboard behavior. The native element has all three.

Incorrect
<div class="select" onclick="openList()">
  <span id="county-val">Choose a county</span>
  <div class="options">…</div>
</div>
Correct
<label for="county">County</label>
<select id="county" name="county">
  <option value="">Choose a county</option>
  <option>Alameda</option>
  <option>Marin</option>
</select>

Tabs that announce their state

When a native element does not exist, ARIA supplies role and state, and script keeps the state current. Here, a clinic services page switches panels.

Incorrect
<div class="tab active" onclick="show('hours')">Hours</div>
<div class="tab" onclick="show('fees')">Fees</div>
Correct
<div role="tablist" aria-label="Clinic information">
  <button role="tab" id="t-hours" aria-selected="true"
          aria-controls="p-hours">Hours</button>
  <button role="tab" id="t-fees" aria-selected="false"
          aria-controls="p-fees" tabindex="-1">Fees</button>
</div>
<!-- arrow keys move between tabs; script omitted -->
<div role="tabpanel" id="p-hours" aria-labelledby="t-hours">…</div>
<div role="tabpanel" id="p-fees" aria-labelledby="t-fees" hidden>…</div>

Toggle buttons with a stable name

A mute button on a hearing-recording player that swaps its icon and its label leaves the screen reader user guessing which state they are in. Keep one name and expose the state.

Incorrect
<button class="mute"><img src="muted.svg" alt="Unmute"></button>
<!-- script swaps the image and alt on click -->
Correct
<button type="button" class="mute" aria-pressed="false">
  <img src="speaker.svg" alt="" />
  Mute
</button>
<script>
  btn.addEventListener("click", () => {
    const on = btn.getAttribute("aria-pressed") === "true";
    btn.setAttribute("aria-pressed", String(!on));
    audio.muted = !on;
  });
</script>

A native range input for something like a search radius is another case: <input type="range"> already exposes its role, value and changes, whereas a draggable div exposes none of them.

Icon controls in native apps

Native toolkits give buttons their role automatically, but only if you use a button. A tappable icon with a gesture recognizer or a plain click modifier has neither a role nor a name.

Incorrect
// SwiftUI
Image(systemName: "printer")
    .onTapGesture { printReceipt() }
Correct
// SwiftUI
Button(action: printReceipt) {
    Label("Print receipt", systemImage: "printer")
}
.labelStyle(.iconOnly)
Incorrect
// Jetpack Compose
Icon(Icons.Default.Print, contentDescription = null,
     modifier = Modifier.clickable { printReceipt() })
Correct
// Jetpack Compose
IconButton(onClick = { printReceipt() }) {
    Icon(Icons.Default.Print, contentDescription = "Print receipt")
}

How to check it

  1. Tab through the page with only the keyboard. Every control should receive focus, and Enter or Space should operate it.
  2. Turn on a screen reader and move through the controls. Each should be announced with a meaningful name, a role, and, where relevant, a state or value.
  3. Operate each control and listen for the change: expanded, selected, checked, value changed.
  4. Open the browser's accessibility tree inspector and compare the name, role and state with what you see on screen.
  5. Use an automated checker to find missing names and misused ARIA, then confirm by hand.

Common mistakes and a ship-it list

  • Using role="button" on a div without keyboard handlers or focusability.
  • Label text hidden with display: none that nothing references through aria-labelledby.
  • Setting aria-expanded once and never updating it.
  • Putting the state into the name, such as "Menu open", instead of a state attribute.
  • A visible label that differs from the accessible name; the label-in-name criterion (2.5.3) covers the related rule.

Ship it when:

  • Every control is a native element, or has a role, name, state and keyboard support.
  • State changes are reflected in attributes the moment they happen.
  • Vendor widgets have been checked against the accessibility tree.
  • Native screens use real buttons and toggles, not tappable images.

Mobile coverage under Title II is discussed in mobile apps.

Sources

Standard version: WCAG 2.1 (W3C Recommendation, 5 June 2018), as named in the Title II web rule. This page is for informational purposes only and is not legal advice. For binding requirements, refer to the official ADA statute and regulations, and ask a lawyer how they apply to you.

Corpowid is recognized by Gartner

Corpowid has been recognized by Gartner, a leading global research and advisory firm, for our innovation and performance in digital accessibility. These badges reflect our commitment to creating inclusive, AI-powered web experiences.

Have questions about Corpowid?

Let’s connect.

We will get back to you as soon as possible.