auditmysitev2.0.2

Click handler without keyboard equivalent

keyboard/click-handler-not-focusable

WCAG success criterion
2.1.1 (level A)
Outcome class
Automatic. An automated check that can report a confirmed violation.
Area
Accessibility, Navigation & Operation
Default severity
high
axe-core rule
click-events-have-key-events

Why we flag this

Keyboard users cannot trigger the function.

onclick on a <div>/<span> without onkeydown/onkeyup, tabindex, or a matching role.

What the rule checks

Scope. Elements with an inline onclick attribute that are not controls by themselves: div, span, p, li, section, article, and a without href.

Expectation. violation once per element with an onclick handler that is neither a control, nor has an interactive role, nor is in the tab order, and contains nothing the Tab key reaches: keyboard users cannot trigger it (F54). When a focusable link or control sits inside (the card around its own link), the function is reachable that way and the finding is needs_review. An a without href is no link and is reported. A role="button" with tabindex="0" passes. An onclick attribute set by script after load counts.

Source. WCAG 2.2 SC 2.1.1 Keyboard; failure F54 (only pointing-device-specific event handlers); HTML §4.5.1 (a without href is a placeholder, not focusable)

Detection: shared; needs dom.

Failing example

The check reports a violation here. Corpus case interaction_click_anchor, element a#ch-anchor. The page may contain further findings for other rules.

interaction_click_anchor.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Placeholder link with a click handler</title>
    <style>
        body { font-family: sans-serif; margin: 0; padding: 16px; color: #111; background: #fff; }
        a, button { display: inline-block; min-width: 44px; min-height: 44px; padding: 12px; box-sizing: border-box; margin: 4px; }
        .skip { position: absolute; left: -9999px; }
        .skip:focus { left: 16px; }
    </style>
</head>
<body>
    <a class="skip" href="#main">Skip to main content</a>
    <header><p>Example Travel</p></header>
    <nav aria-label="Main">
        <a href="/" aria-current="page">Home</a>
        <a href="/trips">Trips</a>
    </nav>
    <main id="main">
        <h1>Your saved trips</h1>
        <!-- violation (2.1.1): an a element without href is a placeholder, not a link
             (HTML §4.5.1, HTML-AAM: role generic), and not focusable -->
        <a id="ch-anchor" onclick="this.textContent = 'Saved'">Save trip</a>
    </main>
    <footer><p>Example Travel footer</p></footer>
</body>
</html>

Passing example

The check passes here and reports nothing. Corpus case interaction_click_pointer, element div#ch-role. The page may contain further findings for other rules.

interaction_click_pointer.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Click handlers and pointer-only controls</title>
    <style>
        body { font-family: sans-serif; margin: 0; padding: 16px; color: #111; background: #fff; }
        a, button, [role="button"] { display: inline-block; min-width: 44px; min-height: 44px; padding: 12px; box-sizing: border-box; margin: 4px; }
        .skip { position: absolute; left: -9999px; }
        .skip:focus { left: 16px; }
        .clickable { cursor: pointer; padding: 12px; margin: 8px 0; border: 1px solid #555; }
        .card { cursor: pointer; padding: 12px; margin: 8px 0; border: 1px solid #555; }
        @media (max-width: 600px) { .mobile-tile { cursor: pointer; } }
    </style>
</head>
<body>
    <a class="skip" href="#main">Skip to main content</a>
    <header><p>Example Travel</p></header>
    <nav aria-label="Main">
        <a href="/" aria-current="page">Home</a>
        <a href="/trips">Trips</a>
    </nav>
    <main id="main">
        <h1>Plan your trip</h1>

        <h2>Inline click handlers</h2>
        <!-- violation: onclick on a div, no role, no tabindex, nothing focusable inside -->
        <div id="ch-div" onclick="this.textContent = 'Opened'">Show itinerary</div>
        <!-- needs_review: the card handler only repeats the link inside it -->
        <div id="ch-card" onclick="location.href = '/trips/rome'">
            <a href="/trips/rome">Rome in three days</a>
        </div>
        <!-- pass: role and tab stop make it operable by keyboard (with a key handler) -->
        <div id="ch-role" role="button" tabindex="0" onclick="this.textContent = 'Shared'" onkeydown="if (event.key === 'Enter' || event.key === ' ') this.click()">Share trip</div>
        <!-- violation: the handler is attached as an attribute after load -->
        <div id="ch-dynamic">Show map</div>

        <h2>Looks clickable</h2>
        <!-- needs_review: cursor pointer, no role, no tabindex, no control inside -->
        <div id="po-filter" class="clickable">Filter results</div>
        <!-- absent: the card pattern, the link inside is the keyboard route -->
        <div id="po-card" class="card">
            <h3><a href="/trips/paris">Paris for families</a></h3>
            <p>Four days, two museums.</p>
        </div>
        <!-- absent: inherits cursor pointer from the reported parent -->
        <div id="po-parent" class="clickable"><span id="po-child">Sort by price</span></div>
        <!-- absent: not rendered -->
        <div id="po-hidden" class="clickable" style="display: none">Hidden filter</div>
        <!-- absent: a real button with a pointer cursor -->
        <button id="po-button" type="button" style="cursor: pointer">Search trips</button>
        <!-- needs_review: looks clickable on the mobile viewport only -->
        <div id="po-mobile" class="mobile-tile">Nearby offers</div>
        <!-- needs_review: a script gives it the pointer cursor after load -->
        <div id="po-dynamic">Recently viewed</div>
    </main>
    <footer><p>Example Travel footer</p></footer>
    <script>
        document.getElementById('ch-dynamic').setAttribute('onclick', "this.textContent = 'Map'");
        document.getElementById('po-dynamic').style.cursor = 'pointer';
    </script>
</body>
</html>

This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.