Skip to lesson content
WCAG 4.1.2 · Level AEasy5 min readForms

Inaccessible Button Patterns

Overview

Buttons must use the <button> element or role="button" with full keyboard support. Using <div> or <span> for buttons is a common accessibility failure.

A <div> styled to look like a button is one of the most common accessibility anti-patterns. To a sighted mouse user it works fine; to everyone else it’s broken—it’s not in the tab order, doesn’t respond to Enter or Space, and isn’t announced as a button. Native <button> gives you keyboard operability, focus, role, and state for free (WCAG 4.1.2); reaching for a div trades all of that away for nothing.

WCAG Criterion:4.1.2
Conformance Level:Level A
Difficulty:Easy
Time to fix:~5 min
Category:Forms

The Problem

This pattern is inaccessible — avoid it.

[ AVOID ]
<div class="btn" onclick="submit()">Submit</div> <span onClick={handleClick} style={{cursor:'pointer'}}>Cancel</span>

The Fix

Use this accessible pattern instead.

[ CORRECT ]
<button type="submit" onClick={submit}>Submit</button> <button type="button" onClick={handleClick}>Cancel</button>

Step-by-step

  1. Use <button> for actions and <a href="..."> for navigation — never use <div> or <span>.

  2. Always set type="submit" or type="button" to prevent unexpected form submission.

  3. If you must use a div (e.g., in a legacy system), add: role="button", tabindex="0", and handle both click and keydown (Enter and Space).

Common Mistakes

  • Using <div> or <span> with onclick as a button.

  • Forgetting type="button" inside a form, causing accidental submits.

  • Re-implementing a div “button” but only handling click, not keydown.

  • Disabling a button visually with CSS while leaving it focusable and clickable.

How to Test for It

  • Tab to the control—a real button is reachable and activates on Enter and Space.

  • Check the accessibility tree shows role “button” with a name.

  • Run an automated scan for clickable elements that aren’t real controls.

Framework Notes

How to apply this fix in your stack.

[ REACT ]
// Correct — native button <button type="button" onClick={handleClick}>Cancel</button> // If forced to use a non-button element: <div role="button" tabIndex={0} onClick={handleClick} onKeyDown={e => ['Enter', ' '].includes(e.key) && handleClick()}>

FAQ