A button is a button element. It is focusable, it takes its place in the tab order without being told to, Enter and Space activate it, it has a disabled state, and a form submits when it is pressed. A div styled to look like a button has none of that until someone writes it, and a machine reading the HTML cannot tell whether anyone did. Check C3, Interactive element semantics, puts four points on that distinction. The landmarks post summarised its rules in a paragraph; this post goes through what the scanner actually sees, what it cannot see, how to test focus and tab order on your own homepage in a minute with no tooling, and what a keyboard trap is and why the point for it is awarded without a test.
What the scanner sees
C3 is worth four points, rated Medium effort, and is scored on the homepage: the first crawled page that returned 200 with a body. The scanner parses the raw HTML, the fetch before any JavaScript runs, and asks three things of it.
First, it counts elements that behave as buttons without being buttons. The selector is exact: a div or span with an onclick attribute, or a div or span with role="button". Nothing else. If that count is zero, two points are earned. The count is reported as clickableNonButtons, and beside it the scanner reports realButtons, the number of button elements on the page, so the ratio is visible at a glance.
Second, it collects every element with a tabindex attribute and counts those whose value is greater than zero. If that count, positiveTabindex, is zero, one point is earned. The check's own comment gives the reasoning: a positive value forces an explicit order that almost always diverges from reading order, so it is treated as unsound rather than merely unusual.
Third, the point for the absence of keyboard traps is awarded without being tested, and the evidence records keyboardTrapsTested: false. The reason is in the last section.
When a point is lost the fix text names the counts, followed by the sentence that summarises the whole check: a div with a click handler is invisible to anything driving the page programmatically. The same count of fake buttons also feeds check C2, whose two points for real anchors require it to be zero, so a single div with role="button" on the homepage costs four points across the two checks.
What the scanner cannot see
The selector matches an onclick attribute, which is the inline form. A handler attached with addEventListener, or through a framework's onClick prop, leaves no attribute in the server-rendered HTML. The div is still not a button, but the scanner has no evidence that it is interactive at all, so it does not count it. role="button" is caught because the role is an attribute and survives rendering.
That makes the scanner's count a floor, not a total, and it describes the agent's position precisely. An agent doing a plain fetch sees the same HTML the scanner sees; a div whose behaviour is attached at hydration is, to that agent, a div with some text in it. Nothing in the page says otherwise. So a zero on clickableNonButtons is a good sign rather than a clean bill, and the manual test below is how to find the rest.
Wrong and right
The patterns the scanner counts, and the ones it cannot:
<!-- Counted by C3 -->
<div class="btn" onclick="openMenu()">Menu</div>
<span role="button" class="btn-primary">Add to basket</span>
<div tabindex="1" onclick="skip()">Skip intro</div>
<!-- Not counted, and still not a button -->
<div class="btn" data-action="open-menu">Menu</div>
<a class="btn">Send</a>
The last line is an a with no href. C3 does not count it, because its selector only looks at div and span; C2 does not count it as an anchor either, because it only counts a[href]. It is focusable by nobody and followable by nothing.
The same controls, as elements that carry their own semantics:
<button type="button" onclick="openMenu()">Menu</button>
<button type="submit">Add to basket</button>
<a href="/contact">Contact</a>
The rule for choosing is short. If activating the control goes somewhere, it is an a with an href. If it does something on the page, it is a button with type="button". If it submits a form, it is a button with type="submit", and the forms post covers why that matters to check C1.
The type="button" is not decoration. Inside a form, a button with no type is a submit button, both to the browser and to the scanner, which treats button:not([type]) as a real submit control. A menu toggle placed inside a form without an explicit type submits the form when pressed. The ARIA Authoring Practices Guide sets out what a custom button has to implement to match the native one: the role, tabindex="0", key handling for Enter and Space, and a disabled state. The scanner counts the div regardless, because it cannot verify that any of that was done, and the native element gets all of it for free.
Testing tab order without tooling
The tab order is simple enough to hold in your head. Elements that are focusable by default, an a with an href, a button, an input, a select, a textarea, are visited in document order. An element with tabindex="0" joins that sequence at its position in the document. An element with tabindex="-1" can be focused by script but is left out of the sequence. Elements with a positive tabindex are visited first, in ascending numeric order, before anything else; a single tabindex="1" pulls one element to the front and pushes every other control behind it.
To test it, open your homepage, click in the address bar so focus starts before the page, and press Tab repeatedly. Watch where the focus indicator goes. Three things to look for:
- Does focus reach every control you could click? A control that focus skips is not focusable, which almost always means it is a
div. This is the manual version ofclickableNonButtons, and it finds the ones the scanner cannot. - Does focus move in the order you would read the page? If it jumps to something in the middle first, look for a positive
tabindex. - Does focus ever disappear? A focused element that is off-screen or hidden by CSS still takes a Tab press, and whoever is stepping through the page is now on something invisible.
When focus is on something that looks like a button, press Enter, then Space. A native button responds to both. A div with a click handler responds to neither, because a keypress is not a click. If the focus indicator is hard to see, type document.activeElement in the console after each Tab and it prints the element. That is the whole toolkit, and it takes about a minute for a homepage.
Keyboard traps
A keyboard trap is a component that focus can enter by keyboard and cannot leave by keyboard. WCAG 2.2 covers it under success criterion 2.1.2, No Keyboard Trap. The usual causes are a modal dialogue that holds focus inside itself, correctly, but offers no Escape key and no close control in the tab order; a custom dropdown or date picker that captures Tab to move between its options and never releases it; and an embedded widget in an iframe that handles its own focus and does not hand it back. For a person, a trap means reloading the page. For an agent driving a rendered page by keyboard, it is a loop: Tab, Tab, Tab, and the set of reachable elements never changes.
The scanner does not test for this, and the check says so. Finding a trap means opening every overlay and widget, stepping through it, and confirming that focus comes out the other side, and that is interaction the crawler does not perform on the sites it visits. Rather than assert a finding it has no evidence for, the check awards the point and records that it did not test. It is a point the rubric grants on trust, labelled as such so nobody mistakes it for a pass. If the crawler gains the ability to test it, the rubric version will be bumped, the changelog will say what changed, and scores issued under the current version will stay as they are.
The manual test is the one above, extended to each thing that opens. Open every dialogue, menu and picker from the keyboard, Tab around inside it, press Escape, and check that focus returns to the control that opened it.
The fix
Search the codebase for role="button" and for click handlers on a div or span, and change each to a button or, where it navigates, to an a with an href. Restyling a button to look like the div it replaces is a few lines of CSS. Remove every tabindex above zero; if the resulting order is wrong, fix it by moving the element in the document rather than numbering it. Then run the Tab test, and run a fresh scan so the change is measured.
C3 is rated Medium because the work is a search through components rather than any single hard change. The implementation service exists for teams who would rather hand that search over, and our own homepage is scored on exactly these rules, with the result public on our live report.
Check your own site
Run the free scan and read the C3 evidence: realButtons, clickableNonButtons and positiveTabindex are the three numbers, and keyboardTrapsTested tells you which point was granted rather than earned. The full scoring for the pillar is on the methodology page under actionability.