The keyboard:unreachable rule flags something that behaves like a control – it has a click handler, or a role such as button or tab – but that no Tab press ever lands on. A mouse user can use it; a keyboard user never gets there. The fix is almost always to use the real element: a <button> for actions, an <a href> for navigation.
What the rule means
WCAG 2.1.1 requires that everything a page lets you do can be done with a keyboard. Native controls – links with an href, buttons, form fields, <summary> – are in the tab order and respond to Enter or Space without any extra code. A <div> or <span> is neither: it gets no focus, and pressing Enter on it does nothing, however it looks and whatever its click handler does.
A role alone does not change that. role="button" tells assistive technology to announce a button, but it does not put the element in the tab order and does not make Enter or Space work. That is a promise the markup does not keep.
Who is affected
Everyone who operates a page without a mouse: people with motor disabilities using a keyboard, switch or mouth stick, blind people using a screen reader, and people using voice control, which often works through the same focusable elements. When the one control that opens a product, adds to the basket or accepts cookies is unreachable, the task cannot be finished at all.
Why the check fails
- Clickable cards – a
<div onclick>around a teaser or a product, with no link inside. - Icon buttons built from
<span>or<i>with a click handler: close, menu, search. - ARIA widgets half-built:
role="tab",role="menuitem"orrole="option"withouttabindexor keyboard handling. tabindex="-1"on a control that was meant to be reachable, often copied from a pattern that manages focus by script.- Cookie banners and chat launchers from third parties that only listen for mouse clicks.
How to fix it
Replace the stand-in with the element that already does the job. You get focus, Enter and Space, and the right role for free.
<!-- Before: a card only a mouse can open -->
<div class="product-card" onclick="location.href = '/products/desk-lamp/'">
<img src="/img/desk-lamp.jpg" alt="">
<h2>Desk lamp</h2>
<p>€49.00</p>
</div>
<!-- After: the heading holds a real link; CSS can stretch it over the card -->
<div class="product-card">
<img src="/img/desk-lamp.jpg" alt="">
<h2><a href="/products/desk-lamp/">Desk lamp</a></h2>
<p>€49.00</p>
</div>
The same for an icon that acts:
<!-- Before: announced as a button, but no Tab reaches it -->
<span class="icon-close" role="button" aria-label="Close" onclick="closePanel()">×</span>
<!-- After: a real button -->
<button type="button" class="icon-close" aria-label="Close" onclick="closePanel()">×</button>
If you really cannot change the element, it needs tabindex="0", a handler for Enter (and Space, for buttons) and the matching role – three things the native element gives you in one.
How to test it manually
- Press Tab from the top of the page and note every control that focus visits.
- Compare with everything you can click: cards, icons, tabs, accordions, sliders, cookie buttons.
- For each one focus skipped, you have found this problem. For each one it visited, press Enter (and Space for buttons) and check it works.
- The browser's accessibility tree (developer tools) shows whether an element is focusable and what role it has.
Related WCAG criterion
2.1.1 Keyboard, Level A. Related: 4.1.2 Name, Role, Value, because a clickable <div> also has no role, and 2.4.3 Focus Order.
How Reviseberg reports it
This rule comes from Reviseberg's keyboard agent, not from axe-core. After walking the journey you choose with real key presses, the agent looks for elements that promise to be controls – an onclick attribute, or a role such as button, link, checkbox, radio, tab, menuitem, switch or option – that are visible but neither focusable by nature nor given a tabindex of 0 or more. Each one is reported with the message "This div behaves like a control but cannot be reached with the keyboard", its selector and the reason it looked interactive.
What it cannot see: a click handler attached with addEventListener leaves no trace in the page that a script can read, so a <div> wired up that way is not reported. The agent under-reports on purpose rather than guessing from a pointer cursor, which would flag every styled card – so a clean result here is not proof, and the manual test above still matters.