How to add hotkeys to your Rails app with Stimulus
Tags: rails, hotwire, stimulus
Published: 2024-02-22 12:41:19

When your app is growing, more screens will be added. It is at this point, you want to consider adding another way for your (power) users to navigate through your app. Introducing: keyboard shortcuts (or also hotkeys)!

Hotkey navigation refers to the implementation of keyboard shortcuts that allow users to quickly navigate through and interact with a web application's interface without relying on mouse clicks.

Luckily there are a ton of libraries out there that make it easy to add this to a Rails app. The one I've been using for all my Rails apps, is [hotkey](https://github.com/github/hotkey) from GitHub. It's an easy-to-use, lightweight, dependency-free library, that, who-would-have-thought, works great with Stimulus. Bingo!

## How GitHub's hotkey library works

The library works great as a vanilla solution. Let's look at the code coming from the README. First there's the HTML:

```html
<a href="/" data-hotkey="g h">Home</a>
```

```js
import {install} from '@github/hotkey'

// Install all the hotkeys on the page
for (const el of document.querySelectorAll('[data-hotkey]')) {
  install(el)
}
```

You need to initialize the JavaScript, but every element with the `data-hotkey` attribute is then turned into a "hotkey". Pretty hot!

This snippet will redirect to the root folder (`/`) after pressing first `g` and then `h`. The library also allows to combine keys, like `Shift+?`.


## Using a Stimulus controller

While above code is really clean, I prefer to use Stimulus. It's a great way to make the code a bit more explicit and remove some indirectness.

How would such a Stimulus controller look like? Just a few lines of code really! When going through below steps, make sure you have the hotkey library available. Either through node (esbuild or bun) or importmaps. The example below uses the node version.


```js
import { Controller } from "@hotwired/stimulus";
import { install, uninstall } from "@github/hotkey";

export default class extends Controller {
  connect() {
    install(this.element);
  }
}
```

And then the HTML:

```html
<a href="/" data-controller="hotkey" data-hotkey="g h">Home</a>
```

Notice that this HTML is a tiny bit more verbose than the previous HTML. It needs the `data-controller="hotkey"` attribute. I would argue this is actually better, as it follows the Stimulus conventions and any code related to hotkeys is sure to be found in the file `app/javacripts/controllers/hotkey_controller.js`.

> Looking for **Global Hotkeys component done for you** (and other UI Components), check out Rails Designer, [the UI Components Library for Rails](https://railsdesigner.com/).


## More than just link redirects

If you use [modals in your Rails app](https://dev.to/railsdesigner/how-to-create-modals-with-rails-and-hotwire-and-tailwind-css-55a7) you are in luck! You can open these using the same controller too. Just be sure to add the correct turbo-frame attributes, like so:

```html
<a href="/help/" data-controller="hotkey" data-hotkey="Shift+?" data-turbo-frame="modal">Help dialog</a>
```

It's that simple: just add `data-turbo-frame="modal"` attribute like you would with any normal link in Rails.


How about **submitting a form using the common `CMD+Enter`**? This can be done as well, but needs a bit more work. That is because, by default, the hotkey library won't fire off the event within form inputs. As that could become annoying quickly.

All you need is to add a `hotkey-scope`. Like so:

```html
  <form>
    <textarea name="message" id="message_content"></textarea>

    <button type="submit" data-controller="hotkey" data-hotkey="Meta+Enter" hotkey-scope="message_content">Submit</button>
  </form>
```

Notice the value of `hotkey-scope` matches the id of the textarea (`message_content`). When the user presses Meta+Enter the form gets submitted. Bam!

And that one small (copy-pasteable) Stimulus controller is all that is needed to get keyboard shortcuts in your Rails app. Other things to explore next are: how to disconnect (uninstall the event listeners/unregister the hotkey) and a way to, optional, disable keyboard shortcuts per user for accessibility reasons (see [this section in the readme](https://github.com/github/hotkey?tab=readme-ov-file#accessibility-considerations)).