Every function on a public-facing site has to work from a keyboard, not only with a mouse or touch. Here is how that plays out in permit, booking and transit tools.
Success Criterion 2.1.1 says all functionality must be operable through a keyboard interface, and that operation must not depend on specific timings for individual keystrokes. A "keyboard interface" is wider than a physical keyboard: switch devices, speech input, on-screen keyboards and screen readers all send key events to the page.
There is one narrow exception: functions whose input depends on the path of a movement and not just its endpoints, such as freehand drawing. Dragging a list item from one position to another is not covered by it, because it has a start and an end, so it needs a keyboard route as well.
The criterion does not require every control to be its own tab stop. A search field that submits when Enter is pressed is operable even though the submit button is never focused. What counts is that nothing can be done only with a pointer.
Government sites tend to be assembled from templates, vendor widgets and years of custom scripts, so the same few patterns come up again and again:
<div> elements with a click handler.These are the barriers a person who cannot use a mouse meets first, and they tend to sit on the pages people must use: payments, bookings and applications. Under the Title II rule, a service a public entity offers online has to be usable without a pointer once the compliance date for that entity arrives.
A native <button> is focusable, activates with Enter and Space, and announces its role. Rebuilding that with a <div> takes tabindex, a role and key handlers, and it is easy to miss one of them.
Example: time slots on a county clinic booking page.
<div class="slot" onclick="pickSlot('2026-03-04T09:30')">
9:30 AM
</div><button type="button" class="slot"
onclick="pickSlot('2026-03-04T09:30')">
9:30 AM
</button>Menus that open on hover need a button that does the same job from the keyboard:
<li class="has-menu" onmouseenter="openMenu(this)">
Services
<ul class="menu">…</ul>
</li><li>
<button type="button" aria-expanded="false"
aria-controls="svc-menu" onclick="toggleMenu(this)">
Services
</button>
<ul id="svc-menu" hidden>…</ul>
</li>Panning and zooming a transit trip planner map is not path-dependent in the sense of the exception: the user only needs to move the viewport somewhere. Provide arrow keys and zoom keys while the map has focus, or visible pan and zoom buttons.
<div id="map"></div>
<script>
map.addEventListener("mousedown", startDrag);
map.addEventListener("mousemove", dragMap);
</script><div id="map" tabindex="0" role="region"
aria-label="Route map. Arrow keys pan, plus and minus zoom."></div>
<script>
map.addEventListener("keydown", e => {
const step = 40;
if (e.key === "ArrowLeft") panBy(-step, 0);
else if (e.key === "ArrowRight") panBy(step, 0);
else if (e.key === "ArrowUp") panBy(0, -step);
else if (e.key === "ArrowDown") panBy(0, step);
else if (e.key === "+") zoomBy(1);
else if (e.key === "-") zoomBy(-1);
else return;
e.preventDefault();
});
</script>Because those single-character keys only work while the map has focus, they stay clear of the character-key shortcut rules in 2.1.4.
Mobile apps are in scope of the Title II rule, and people do use Bluetooth keyboards, Full Keyboard Access on iOS, and switch devices on Android. A view that only listens for taps through a gesture recognizer is invisible to them.
SwiftUI, a park pass row:
HStack {
Image(systemName: "ticket")
Text("Reserve day pass")
}
.onTapGesture { reserve() }Button(action: reserve) {
Label("Reserve day pass", systemImage: "ticket")
}Jetpack Compose, the same row:
Box(
Modifier.pointerInput(Unit) {
detectTapGestures(onTap = { reserve() })
}
) { Text("Reserve day pass") }Button(onClick = { reserve() }) {
Text("Reserve day pass")
}Tab through the whole task, for example booking an appointment from start to confirmation.Enter on links, Space or Enter on buttons, arrow keys inside menus, tabs and radio groups, and Esc to close overlays.Automated checkers can flag missing roles or focusable elements without names, but they cannot know that a plain <div> has a click handler, so the manual pass is the real test.
tabindex="0" on a <div> and stopping there, without Enter and Space handling.<a> without href for actions, which removes it from the tab order.display: none when it is still needed, or removing focus with a script.See also the guidance on common barriers that DOJ lists, which includes mouse-only navigation.