---
title: "How We standardized keyboard shortcuts in neeto"
description: "How we standardized keyboard shortcuts in neeto"
canonical_url: "https://www.bigbinary.com/blog/how-we-standardized-keyboard-shortcuts"
markdown_url: "https://www.bigbinary.com/blog/how-we-standardized-keyboard-shortcuts.md"
---

# How We standardized keyboard shortcuts in neeto

How we standardized keyboard shortcuts in neeto

- Author: Adil Ismail
- Published: June 27, 2023
- Categories: ReactJS

We are building [Neeto](https://neeto.com), which is a collection of software.
Keyboard shortcuts are a vital feature in any product that aims to improve user
experience. This blog will discuss how we standardized keyboard shortcuts across
all Neeto products.

## The need for standardization

Before we delve into how we've standardized keyboard shortcuts, it's important
to understand why we felt the need to do so. With multiple products under the
Neeto ecosystem, each product team was implementing keyboard shortcuts
functionality in their own way. This led to several problems:

- Cross-platform browser problems

  Some products use `react-hotkeys` and some use `react-hotkeys-hook` for
  keyboard shortcuts. And some create their custom hooks to handle keyboard
  shortcuts. However, the issue with all three approaches is that developers may
  miss adding alternative hotkeys for operating systems that are different from
  their development machine.

  Furthermore, OS-based key translation behavior is inconsistent. For instance,
  one product may use the combination `option + s` for Mac and `alt + s` for
  Windows, while another product may use `option + s` for Mac and `ctrl + s` for
  Windows. Such inconsistencies degrade the user experience when users switch
  between operating systems.

- Difference in UI for listing all shortcuts

  Each product displayed shortcuts differently. Some used `react-hotkeys`
  built-in feature, while others created separate pages, modals, or tooltips to
  show hotkeys. We wanted consistent behavior across all products in Neeto.

- Difficulty finding and modifying registered hotkeys in the codebase

  There was no standard convention on how/where to keep hotkey data. Some
  products have created a file to store all the hotkeys & related info, and some
  have their own conventions. When a developer is transferred from one product
  to another, they find it hard to adapt to the new conventions over there. We
  wanted consistency in code structuring as well for all products in Neeto.

To fix all these problems, we decided to bring in some code conventions and
extract the common code to an npm package so that it can be reused in all
products consistently.

## Components and hooks created for standardization

We introduced a custom hook to run a specified function when the associated
hotkey is fired. We also built a side pane component to display the list of all
shortcuts.

### ShortcutsPane

ShortcutsPane is a React component that displays all the keyboard shortcuts in
the product. It belongs to `neeto-molecules` package to enable code reuse. Users
can open/close the shortcuts pane by clicking on the Keyboard shortcuts icon in
the sidebar or pressing a hotkey `shift+/`.

This is how the pane looks:

![Pane image](https://user-images.githubusercontent.com/29166133/234621934-09a75528-a1e7-4840-9fe9-fd51085a3a3c.png)

This is how the component is integrated into the products:

```jsx
<KeyboardShortcuts.Pane productShortcuts={SHORTCUTS}>
```

We include `KeyboardShortcuts.Pane` in the `Main` component, which, as per Neeto
standards, is the topmost parent component. Here, `SHORTCUTS` constant contains
the product-specific custom shortcuts. We have set a convention to define it in
a file named `constants/keyboardShortcuts.js`. `productShortcuts` prop is
optional and it can be omitted if a product doesn't have any custom shortcuts.

Given below is the sample structure of the `keyboardShorcuts.js` file.

```js
export const CATEGORY_NAMES = {
  messenger: "MESSENGER",
  settings: "SETTINGS",
};

export const SHORTCUTS = {
  [CATEGORY_NAMES.messenger]: {
    addNewLine: {
      sequence: "shift+return",
      description: "Add new line",
    },
    sendMessage: {
      sequence: "return",
      description: "Send message",
    },
    addEmoji: {
      sequence: "command+option+e",
      description: "Add emoji",
    },
    closeConversation: {
      sequence: "command+option+y",
      description: "Close conversation",
    },
  },
  [CATEGORY_NAMES.settings]: {
    addPlaceholder: {
      sequence: "command+option+t",
      description: "Add placeholder",
    },
  },
};
```

Because of this consistency, the developers moving from one Neeto product to
another Neeto product would find it easy to identify and make changes to the
product shortcuts.

When `SHORTCUTS` is passed into `KeyboardShortcuts.Pane`, it will be merged with
the common keyboard shortcuts list. The common list will contain shortcuts like
open/close pane, close modals, submit form, etc that apply to all products.
Developers have to pass the `hotkey` for MacOs and the component will take care
of identifying the user's platform using `platform.js` and render appropriate
platform-specific keys. For example, `option` will be converted to `alt` for
Windows users.

### useHotKeys hook

We wanted a hook that combined the features of both `react-hotkeys` and
`react-hotkeys-hook`. We wanted all the features of `react-hotkeys-hook` like
their hook style & easy to use API and the `sequential` mode of `react-hotkeys`
package. So we went with building our own hook named `useHotKeys`. We added the
hook to the package `@bigbinary/neeto-commons-frontend`. All our reusable hooks,
common utility functions, configs, etc reside in this package. Developers will
pass a hotkey, a handler function, and an optional configuration object to the
hook.

`useHotKeys` is built using the popular
[mousetrap.js](https://craig.is/killing/mice) package. `mousetrap.js` is a tiny
library that helps handle keyboard shortcuts in an application.

Because of the following features of `useHotKeys`, we can handle most of the
shortcut management cases for any application.

- It supports sequential hotkeys. For example, "press s and then r".
- It can bind simultaneous hotkeys. For exampl,e "press s and at the same time
  press r".
- It can bind single hotkeys eg: `return`.
- It auto converts a hotkey based on platform eg: `command` is converted to
  `ctrl` on Windows, and the user will only pass the hotkey for macOS.
- It supports `enabled` config which can be used to enable/disable the hotkey.
  By default, all hotkeys will be enabled.
- It supports multiple modes of operation as explained below:
  - `default`: On default mode hotkeys won't be fired if the current focus is on
    input fields.
  - `scoped`: The mode scoped is used to restrict a hotkeys handler to a
    specific DOM element eg: forms.
  - `global`: global is similar to the default mode. The only difference is that
    it will fire even if the user is focused on an input field.

The following is an example of using the default mode. Here, the handler
`handleCloseConversation` will be invoked whenever the user presses the close
conversation hotkey:

```jsx
useHotKeys(MESSENGER_SHORTCUTS.closeConversation.sequence, () =>
  setCoversationModalOpen(true)
);
```

We define constants like `MESSENGER_SHORTCUTS` in the
`constants/keyboardShortcuts.js` file to make it easier to access sequences and
pass them to the `useHotKeys` hook. It would look something like this:

```js
export const MESSENGER_SHORTCUTS = SHORTCUTS[CATEGORY_NAMES.messenger];
```

The following is an example of using the scoped mode. When the mode is scoped,
`useHotKeys` will return a React ref that we can attach to the desired element.
The handler function will only be invoked when the user presses the hotkey and
is focused inside the form, e.g, the input element:

```jsx
const formRef = useHotKeys(
  MESSENGER_SHORTCUTS.toggleModal.sequence,
  () => setIsModalOpen(isOpen => !isOpen),
  { mode: "scoped" }
);

return (
  <div>
    <form ref={formRef}>
      <input type="text" name="username" />
    </form>
  </div>
);
```

### usePaneState hook

While rolling out these components to products, we noticed that it would be nice
if some pages were styled differently when the pane is open. So, we added a
hook, `usePaneState`, that will tell whether the pane is open or not. Pages can
use this hook to know the current status of the pane & apply styles accordingly.

Try out any of the [neeto](https://neeto.com) products and see the keyboard
shortcuts in action for yourself.

## Links

- [Human page](https://www.bigbinary.com/blog/how-we-standardized-keyboard-shortcuts)
