Free guideEditorial review in progress; your agent sees whether each guide's current version is reviewed. What “reviewed” means

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.

Discipline
Systems & data
Type
Anti-pattern
Platforms
Website, Web app
Version
1.0.0 · 2026-09-24

Overview

Symptoms.

  • <div onClick={…}>Save</div> or a <span> styled as a button; no tabindex, no role, no Enter/Space handling.
  • A "dropdown" made of divs: no role="combobox"/listbox/option, no aria-expanded, no arrow-key navigation, no aria-activedescendant or roving tabindex; options invisible to screen readers.
  • Drag handles, custom sliders, carousel arrows or "click to expand" cards with onClick only and no onKeyDown or 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

  1. Buttons: replace with <button type="button">; if the element navigates, <a href>. Only if a native element is impossible, add role="button" tabindex="0" and handle Enter and Space.
  2. 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 (see sys-combobox-implementation-contract).
  3. Every interactive element gets a keyboard path: Enter/Space at minimum; arrow keys for composite widgets; visible focus; never outline: none without a replacement.
  4. Drag, swipe and slider interactions get a single-pointer, non-gesture alternative and keyboard equivalents (arrow keys on role="slider" with aria-valuenow).
  5. Lint and test: enable jsx-a11y rules (click-events-have-key-events, no-static-element-interactions, no-noninteractive-tabindex); add a keyboard-only end-to-end test per widget.
  6. Audit query: search for onClick on div/span/li/img and for cursor: pointer on 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-expanded and 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.