Skip to content

JavaScript Tutorial Accessibility with JavaScript

Keyboard Interaction

Lesson 216 of 258 3 min read By Deepak Paswan Updated Oct 4, 2026

Many people never use a mouse: keyboard-only users, screen reader users, people with motor difficulties, power users. If a feature only works with a mouse, it does not work for them. JavaScript is often where accessibility gets lost, so build it in from the start.

The golden rule: use the right element first

Native elements come with keyboard support for free.

NeedUseYou get for free
An action<button>Tab focus, Enter and Space activate it
Navigation<a href>Tab focus, Enter follows it
A choice<input type="checkbox">, radio, <select>Space, arrows, labels
A popup<dialog>focus handling, Escape to close
Expandable text<details>Enter / Space toggle

A <div onclick> has none of this. It cannot be focused, and Enter does nothing.

index.html
HTML
<style>
  .fake { display: inline-block; padding: 6px 12px; background: #ddd; cursor: pointer; margin-right: 8px; }
</style>
<div class="fake" id="fake">Fake button (div)</div>
<button id="real">Real button</button>
<p id="out">Press Tab, then Enter, in the result.</p>
<script>
  const out = document.querySelector("#out");
  document.querySelector("#fake").addEventListener("click", () => { out.textContent = "div clicked (mouse only)"; });
  document.querySelector("#real").addEventListener("click", () => { out.textContent = "button activated (mouse or keyboard)"; });
</script>

Try tabbing: the real button gets focus and works with Enter and Space. The div is skipped completely.

Keys users expect

KeyExpected behaviour
Tab / Shift+Tabmove to the next / previous interactive element
Enteractivate a link or button, submit a form
Spaceactivate a button, toggle a checkbox, scroll the page
Escapeclose a dialog, menu or popup, cancel
Arrow keysmove within a widget: tabs, menus, radio groups, sliders, lists
Home / Endfirst / last item in a list or menu

Arrow-key navigation (roving tabindex)

In a group like a toolbar or tab list, only one item is in the Tab order. Arrow keys move between items.

index.html
HTML
<div role="toolbar" aria-label="Text formatting" id="bar">
  <button tabindex="0">Bold</button>
  <button tabindex="-1">Italic</button>
  <button tabindex="-1">Underline</button>
</div>
<script>
  const buttons = [...document.querySelectorAll("#bar button")];

  document.querySelector("#bar").addEventListener("keydown", event => {
    const index = buttons.indexOf(document.activeElement);
    if (index === -1) return;

    let next = index;
    if (event.key === "ArrowRight") next = (index + 1) % buttons.length;
    else if (event.key === "ArrowLeft") next = (index - 1 + buttons.length) % buttons.length;
    else if (event.key === "Home") next = 0;
    else if (event.key === "End") next = buttons.length - 1;
    else return;

    event.preventDefault();
    buttons[index].tabIndex = -1;
    buttons[next].tabIndex = 0;
    buttons[next].focus();
  });
</script>

tabindex

ValueEffect
tabindex="0"makes an element focusable, in the natural order
tabindex="-1"focusable by script (el.focus()), but not by Tab
tabindex="1" or higheravoid: it breaks the natural order

Custom controls must be completed

If you really must make a custom control (rare), you need all of: a role, tabindex="0", keyboard handlers for Enter and Space, a visible focus style and proper ARIA state. Compare that to <button>: it is almost always better to use the real element and style it.

Test it

Unplug the mouse (or just stop using it). Can you reach and use every control, see where focus is, and escape from every popup? If not, fix that first.

dangerous
Do not remove focus outlines
outline: none without a replacement leaves keyboard users lost. See Focus Styles in the CSS course.