Anti-pattern: div buttons, div selects, and components with no keyboard path
Clickable <div>/<span> elements without roles or key handlers, custom selects built from styled divs with only onClick, and sliders/carousels/expandable cards that work only by mouse or touch exclude keyboard and assistive-technology users entirely — and are the most common serious accessibility failures in component libraries.
Overview
Symptoms.
<div onClick={…}>Save</div>or a<span>styled as a button; notabindex, norole, no Enter/Space handling.- A "dropdown" made of divs: no
role="combobox"/listbox/option, noaria-expanded, no arrow-key navigation, noaria-activedescendantor roving tabindex; options invisible to screen readers. - Drag handles, custom sliders, carousel arrows or "click to expand" cards with
onClickonly and noonKeyDownor focusable element. - A low-contrast "reject"/"decline" control implemented this way — an accessibility failure that also functions as a deceptive obstruction.
Why harmful. Browsers add no keyboard support to custom ARIA widgets; a div has no role, no focus, no keyboard activation and no announcement, so the control does not exist for keyboard and screen-reader users. Custom selects compound two failures at once (div triggers plus a missing focus-management strategy for the popup). The APG combobox and listbox patterns exist precisely because this category is hard to get right from scratch.
Use this item when: reviewing PRs, auditing a library, or a linter flags a click handler on a non-interactive element.
Implementation
- Buttons: replace with
<button type="button">; if the element navigates,<a href>. Only if a native element is impossible, addrole="button" tabindex="0"and handle Enter and Space. - Selects/comboboxes: use native
<select>(customisable select where the browser matrix allows), or a headless Select/ComboBox; if hand-rolled, implement the full APG contract (seesys-combobox-implementation-contract). - Every interactive element gets a keyboard path: Enter/Space at minimum; arrow keys for composite widgets; visible focus; never
outline: nonewithout a replacement. - Drag, swipe and slider interactions get a single-pointer, non-gesture alternative and keyboard equivalents (arrow keys on
role="slider"witharia-valuenow). - Lint and test: enable
jsx-a11yrules (click-events-have-key-events,no-static-element-interactions,no-noninteractive-tabindex); add a keyboard-only end-to-end test per widget. - Audit query: search for
onClickondiv/span/li/imgand forcursor: pointeron non-interactive elements; fix or justify each.
Verification
- Lint reports zero click handlers on non-interactive elements without keyboard handlers.
- Every control is reachable and operable with Tab/Enter/Space/arrows; recorded walkthrough.
- Custom selects expose roles,
aria-expandedand a focus strategy; screen reader announces options. - Gestures and drags have button/keyboard alternatives.
- Focus is visible on every control (no unreplaced
outline: none).
Sources:
Limitations
- Some third-party embeds ship these failures; wrap them with an accessible alternative or replace them.
- Native apps have the analogous failure (custom views without accessibility traits); see
app-anti-pattern-custom-controls-without-accessibility.