Focus keys

Assign keyboard shortcuts that focus an item or panel from anywhere under the listener, across groups and even from inside a text field.

Set data-keyrove-focus-key on an element to focus it with a shortcut from anywhere under the listener.

Ctrl+Shift+1, 2 or 3 focuses a panel in this demo. Try a shortcut from the text area: focus keys with Ctrl, Alt or Meta also work inside editable fields.

Live · editor panes
Explorer
Editor
Terminal
<div id="editor-panes">
  <section data-keyrove-focus-key="ctrl+shift+Digit1" tabindex="0">
    Explorer
  </section>
  <section data-keyrove-focus-key="ctrl+shift+Digit2" tabindex="0">
    Editor
    <textarea
      rows="3"
      placeholder="Type here — the chords still reach out"
    ></textarea>
  </section>
  <section data-keyrove-focus-key="ctrl+shift+Digit3" tabindex="0">
    Terminal
  </section>
</div>

The panels are not navigation items, so arrows do not move between them. Each panel has its own combo. A bare code also works: data-keyrove-focus-key="KeyE" focuses an element with E outside editable fields.

The move reports action: 'focus' to onMove and in the return value.

As far as the listener hears

keyrove looks for focus keys on every element under the listener's element, not just in the nearest root, so where you attach the listener is the only scope control:

  • On a panel, the keys work inside that panel.
  • On document, they work page-wide.
  • From inside a nested root, a focus key outside it still fires. Focus keys are the third way out of a nested group, after Tab and a key of your own.

A key and an order

An element can have both data-keyrove-item and a focus key. This palette supports arrow navigation and direct shortcuts to tools.

Live · drawing tools
<div
  id="tools"
  role="toolbar"
  aria-orientation="vertical"
  aria-label="Drawing tools"
>
  <button
    data-keyrove-item
    data-keyrove-roving-tabindex
    data-keyrove-focus-key="KeyV"
    aria-keyshortcuts="V"
    tabindex="0"
  >
    Move
  </button>
  <button
    data-keyrove-item
    data-keyrove-roving-tabindex
    data-keyrove-focus-key="KeyF"
    aria-keyshortcuts="F"
    tabindex="-1"
  >
    Frame
  </button>
  <!-- … -->
  <button
    data-keyrove-item
    data-keyrove-roving-tabindex
    data-keyrove-focus-key="KeyH"
    aria-keyshortcuts="H"
    tabindex="-1"
  >
    Hand
  </button>
</div>

Press P for the pen, then ↓: the arrow steps on from wherever the key landed, because the pen is a stop in the same order the arrows walk. The readout tells the two apart, focus for the jump and next for the step. Then Tab out of the palette and Shift+Tab back: focus returns to the tool you last reached, whichever way you reached it, because the roving tab stop follows a jump as it follows an arrow.

Focus keys use the bindings you choose: V for Move, O for Ellipse, I for Eyedropper. Typeahead instead matches labels.

Each tool also has aria-keyshortcuts. The demo uses it to display the shortcut beside the label; see telling users about it.

A focus key only moves focus. To select or activate the tool, add your own logic, for example in onMove using the destination to.

Item or not

A focus key needs no data-keyrove-item, and whether its element has one decides what else reaches it:

  • An item, like a tool in the palette, stays in its group's order, so the arrows reach it too, and the key is a shortcut to a place they already go. The jump is a move in that group: from is the item focus left, or null from outside the group, and a roving tab stop moves when focus leaves a roving item. Attach followFocus to focusin to update the stop when the shortcut enters from outside the group.
  • Any other element, like the panels, is reached by its key alone. No arrow lands on it. A jump to it reports from: null and moves no group tab stop. If focus is already inside it, from is that element and to is null.

Either way, pressing the key while focus is already inside its element is a consumed no-op: the key is claimed, and focus stays where it is. The API reference has the exact rules.

A panel with a list inside is best made that list's root: put data-keyrove-root on the panel itself. The jump leaves focus on the panel, and an arrow pressed there then enters the panel's own list. Without the root, the arrow belongs to the group around the panels and lands on the first item under the listener, whichever panel that is in.

<section
  data-keyrove-root
  data-keyrove-focus-key="ctrl+shift+Digit1"
  tabindex="-1"
>
  <ul>
    <li data-keyrove-item tabindex="-1">index.ts</li>
    <li data-keyrove-item tabindex="-1">README.md</li>
  </ul>
</section>

The panel's tabindex="-1" is what lets it take focus while keeping it out of the Tab order. Without it, or on any element that cannot take focus, the key is still claimed, but focus stays where it was and onMove does not fire.

From inside a text field

Movement bindings leave editable targets their editing keys. Focus shortcuts are the exception:

  • A combo holding Ctrl, Alt or Meta fires from inside a field.
  • A bare combo, or one holding only Shift, does not.

In the demo, ctrl+shift+Digit1 reaches out of the text area; a bare Digit1 would type a "1" there and focus the panel from everywhere else.

Choose shortcuts that do not conflict with editing commands or text input. For example, Ctrl+B can mean bold; Alt+letter can type accented characters on macOS; and Windows can report AltGr as Ctrl+Alt. A ctrl+alt+ focus shortcut can therefore fire during text entry. No focus key runs while isComposing is true.

Precedence and ties

Focus keys take precedence over explicit movement bindings and defaults. For example, an element's Home focus key overrides the usual Home action.

When two elements declare the same combo, the first in DOM order wins. The attribute scan excludes skipped and disabled targets. An explicit focusKeys map replaces that scan and bypasses skip checks, but still excludes disabled targets; see configuration differences.

If a target is excluded, another binding may handle the key. If none matches, the browser keeps it. Consider the listener's scope when assigning shortcuts: a bare letter on a document listener applies across the page.

Telling users about it

Add aria-keyshortcuts to expose a shortcut to assistive technology. keyrove does not set it for you. ARIA uses key names, while keyrove uses physical codes: data-keyrove-focus-key="ctrl+shift+KeyE" pairs with aria-keyshortcuts="Control+Shift+E".

Search documentation

↑ ↓ to selectEnter to openEsc to close