Every control must expose a name, role, state and value to assistive technology. See where vendor widgets and custom components fall short.
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.
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 set of divs that imitates a select has no role, no value and no keyboard behavior. The native element has all three.
<div class="select" onclick="openList()">
<span id="county-val">Choose a county</span>
<div class="options">…</div>
</div><label for="county">County</label>
<select id="county" name="county">
<option value="">Choose a county</option>
<option>Alameda</option>
<option>Marin</option>
</select>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.
<div class="tab active" onclick="show('hours')">Hours</div>
<div class="tab" onclick="show('fees')">Fees</div><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>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.
<button class="mute"><img src="muted.svg" alt="Unmute"></button>
<!-- script swaps the image and alt on click --><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.
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.
// SwiftUI
Image(systemName: "printer")
.onTapGesture { printReceipt() }// SwiftUI
Button(action: printReceipt) {
Label("Print receipt", systemImage: "printer")
}
.labelStyle(.iconOnly)// Jetpack Compose
Icon(Icons.Default.Print, contentDescription = null,
modifier = Modifier.clickable { printReceipt() })// Jetpack Compose
IconButton(onClick = { printReceipt() }) {
Icon(Icons.Default.Print, contentDescription = "Print receipt")
}role="button" on a div without keyboard handlers or focusability.display: none that nothing references through aria-labelledby.aria-expanded once and never updating it.Ship it when:
Mobile coverage under Title II is discussed in mobile apps.