Skip to content

A zero-JavaScript solution for moving focus with arrow keys

Notifications You must be signed in to change notification settings

mecha/arrowhead

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arrowhead

Move focus between DOM elements using the arrow keys, without writing any JavaScript!

Installation

Arrowhead is a single, dependency-free JavaScript file. Simply download the script and include it in your page using a <script> tag:

<script src="path/to/downloaded/arrowhead.min.js"></script>

Usage

Focus moves between elements marked with the ah-item attribute.

Use the ah-row and ah-col attributes to set the arrow key axis. Rows use the left/right ⇦/⇨ keys, while columns use the up/down ⇧/⇩ keys:

<div ah-row>
  <button ah-item>press left</button>
  <button ah-item>focused</button>
  <button ah-item>press right</button>
</div>

<div ah-col>
  <button ah-item>press up</button>
  <button ah-item>focused</button>
  <button ah-item>press down</button>
</div>

Items do not need to be on the same level:

<div ah-col>
  <button ah-item>press up</button>
  <div>
    <ul>
      <li><button ah-item>focused</button></li>
      <li><button ah-item>press down</button></li>
    </ul>
    <button ah-item>press down x2</button>
  </div>
</div>

By default, items can be 5 levels deep from a ah-row or ah-col element. This can be changed using the ah-depth attribute:

optimize performance
<div ah-col ah-depth="1">
  <div>...</div>
  <button ah-item>press up</button>
  <button ah-item>focused</button>
  <button ah-item>press down</button>
  <div>...</div>
</div>

or go crazy
<div ah-col ah-depth="24">
  <div>
    <div>
      <div>
        ...
        <button ah-item>...</button>
        ...
      </div>
    </div>
  </div>
</div>

Rows and columns can be nested. Focus can move between sibling rows and columns varying types:

<div ah-col>
  <div ah-row>
    <button ah-item>press up</button>
    <button ah-item>press up, right</button>
    <button ah-item>press up, right, right</button>
  </div>
  <div ah-row>
    <button ah-item>press left</button>
    <button ah-item>focused</button>
  </div>
  <div ah-row>
    <button ah-item>press down</button>
    <button ah-item>press down, right</button>
    <button ah-item>press down, right, right</button>
  </div>
</div>

Elements now marked with any Arrowhead attributes are not affected by Arrowhead.

Check out the demo.

Auto

Arrowhead can automatically assign the ah-item attribute to the child elements of an element. This is done by adding the ah-auto attribute to the parent and providing a CSS selector string.

This can be particularly useful when you do not have full control over the rendered HTML.

...
<div ah-auto="a, button">
  <p>This <a href="...">link</a> will be given the ah-item attribute.</p>
  <button>This button too!</button>
  <input value="But not this field" />
</div>
...

This becomes:

...
<div>
  <p>This <a href="..." ah-item>link</a> will be given the ah-item attribute.</p>
  <button ah-item>This button too!</button>
  <input value="But not this field" />
</div>
...

If the ah-auto attribute has no value or an empty one, the CSS selector a, button, input, textarea, select, summary will be used.

Auto is not enabled by default. You can "enable" it by running ArrowHead.auto() when the page content is ready. This function takes the root of the tree to be searched for ah-auto attributes.

document.addEventListener("DOMContentLoaded", function () {
  ArrowHead.auto(document.body);
});

If you're using [htmx], you may also want to run auto() on any new content that gets swapped into the DOM:

document.addEventListener("htmx:afterSwap", function (ev) {
  if (ev.target instanceof HTMLElement) {
    ArrowHead.auto(ev.target);
  }
});

How it works

Arrowhead is basically just a key listener on the document.

When an arrow key is pressed, Arrowhead will begin searching upwards in the DOM, starting from the currently focused element. The search stops when an element has the ah-layout attribute or the ah-row and ah-col shorthands:

<div ah-layout="row">...</div>
<div ah-layout="col">...</div>

<div ah-row>...</div>
<div ah-col>...</div>

When found, these enable Arrowhead navigation in their subtree, in either the horizonal or vertical axes:

row col
←/→ ↑/↓

If the axis of the pressed key does not match the axis of the found layout, Arrowhead repeats the search starting from the found layout element.

When a matching layout is found, a direction is determined as follows:

←/↑ →/↓
Backwards (BWD) Forwards (FWD)

Arrowhead now begins a search in the layout's subtree for all descendant layout elements and elements marked with the ah-item attribute (value unused). All other elements will be recursively searched up to a depth of 5. The depth can be changed by adding ah-depth attribute to the layout element.

The search stops when it finds the currently focused element. Then, depending on the direction, the search will either yield the previous search result (BWD) the next one (FWD), if they exist.

If a result is not found, Arrowhead will restart the process using the layout element as the currently focused element. This way, the key event "bubbles up".

If a result is found, that element becomes the target.

  1. If the target is a ah-item element, it recieves focus.
  2. If the target is a layout element, focus will be given to the layout's first or last item element, depending on the direction. If this element is also a layout, this process repeats.
  3. If the target is any other element, nothing happens.
<div ah-layout="col">
  <br />
  <div>
    <button ah-item>focused if up key is pressed</button>
  </div>
  <br />
  <button>currently focused</button>
  <br />
  <ul>
    <li>
      <button ah-item>focused if down key is pressed</button>
    </li>
  </ul>
  <br />
</div>

About

A zero-JavaScript solution for moving focus with arrow keys

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published