React-React keyboardist: An easy and declarative way to add keyboard shortcuts to your React app.

? React Keyboardist

React Keyboardist offers a simple and declarative way to add keyboard shortcuts to your react applications. It is just a React Wrapper for ? Keyboardist.

Click here for a simple demo

TOC

Installation

$ yarn add react-keyboardist

or

$ npm install --save react-keyboardist

Global Listener

The default export <Keyboardist/> provides a global listener attached to the document element that will listen for every key event except those that happen inside input elements (e.g. <input/>,<textarea/>). This is useful, for example, to have keystrokes that activate different parts of your UI.

If you need to listen for keyboard events inside an input using a similar API, React Keyboardist also comes with a KeyboardInput component.

How to use

Just pass a dictionary with the shape of {keyName : [function]} via the bindings property and they will be automatically binded when the component mounts.

import Keyboardist from 'react-keyboardist';

class App extends React.Component {
  //... state methods would go here

  render() {
    return (
      <React.Fragment>
        <Keyboardist
          bindings={{
            Slash: this.focusSearch,
            Period: this.showMenu,
            Escape: this.logOut,
            KeyK: this.next,
            KeyJ: this.prev,
          }}
        />
        <RestOfTheApp />
      </React.Fragment>
    );
  }
}

All the subscription objects will be automatically unsuscribed when the component unmounts so you can use it as any other component in your component hierarchy.

Other Events

By default React Keyboardist will listen to keydown events, but you can use keyup instead.

<Keyboardist
  eventName="keyup"
  bindings={{
    Slash: this.focusSearch,
    Period: this.showMenu,
    Escape: this.logOut,
    KeyK: this.next,
    KeyJ: this.prev,
  }}
/>

Multiple listeners for a Key

You can add multiple listeners for the same key, and they will be executed starting from the last one. If one of the listeners returns false the execution chain will stop. This is useful when you want to override a key in a child component.

const App = ({ openDialog, closeDialog, isDialogOpen, handleLogout }) => (
  <div>
    <Keyboardist
      bindings={{
        Enter: openDialog,
        Escape: handleLogout,
      }}
    />
    {isDialogOpen && <ModalDialog onClose={closeDialog} />}
  </div>
);

const ModalDialog = ({ onClose }) => {
  const bindings = {
    Escape: () => {
      onClose();
      // this will prevent the Escape binding in the parent component to be triggered.
      return false;
    },
  };
  return (
    <div className="dialog">
      <Keyboard bindings={bindings} />
      <DialogContentOrWhatever />
    </div>
  );
};

Event Monitor

The monitor property allows you to either pass a monitor function or just set it to true to use Keyboardist's default monitor. You can read more about Keyboardist monitor over here.

<Keyboardist
  bindings={bindings}
  monitor={(keyName, matched) => {
    // do something
  }}
/>

KeyboardInput

Sometimes you want to listen to key events inside an input or textarea element, for example, to make a keyboard-enabled live search or to add keyboard functions inside an editor.

How to use

KeyboardInput has pretty much the same API as the global listener, except that instead of not rendering anything, it will render either an <input/> component (by default) or a <textarea/> component.

The properties for KeyboardInput are bindings, eventName, monitor and component, every other property will be forwarded to the rendered component.

import { KeyboardInput } from 'react-keyboardist';

class App extends React.Component {
  //... state methods would go here

  render() {
    return (
      <React.Fragment>
        <KeyboardInput
          className={'tag-selector'}
          onFocus={this.showOptions}
          bindings={{
            Up: this.prevOption,
            Down: this.nextOption,
            Space: this.selectOption,
          }}
        />
        <RestOfTheApp />
      </React.Fragment>
    );
  }
}

Textarea

If you want the component to render a textarea element instead of an input, you can use the component property.

<Keyboardist component={'textarea'} bindings={bindings} />

Examples

If you want to see all the capabilites of React Keyboardist, here's a really contrived demo and you can find the source for that in the docs folder.

React Router + Keyboardist

If your application is some kind of Admin Dashboard, you may be using React-Router for the different sections of the app. React Router + Keyboardist offers a drop-in replacement for the Route component that allows to assign a keyboard shortcut to every route.

Here's a blog post with the reasoning behind it.

Comments

  • Nested listeners for same key
    Nested listeners for same key

    Feb 17, 2020

    Greetings. I have listener on all the pages in my React app (see App.jsx below) for the right-arrow key, which simply moves the user to the next page (I'm calling this the 'global' listener for lack of a better name).

    [note that there is similar ticket in 'keyboardist, but I am actually using this package]

    However I want to override this for a specific page in the App. So I added a Keyboardist binding to the render function of that page (2nd code chunk below).

    By returning false I expected that ONLY the 'local' listener function would be called, but what I find is that BOTH 'global' and 'local' listeners are called.

    How to stop the event from propagating up to the global listener from the local listener ? thanks!

    class App extends React.Component {
      render() {
        return (
              <div className="App">
                <header className="App-header">
                  <Router>
                    <React.Fragment>
                      <Keyboardist bindings={{ Right: () => console.log('GLOBAL_KEY_LISTENER') }} />
                      <Navigation />
                      <Route exact path="/" component={StartPage} />
                      <Route exact path="/smash" component={SmashPage} />
                      ...
    
      render() {
        return (
          <div className={this.props.root}>
            <Keyboardist bindings={{
              Right: () => {
                console.log('LOCAL_KEY_LISTENER');
                return false;
              }
            }} />
           ...
    
    Reply
  • How to programmatically trigger a key
    How to programmatically trigger a key

    Dec 26, 2019

    I've set up some key bindings that are working fine, but how can I trigger those keys from js code? thanks

    Reply
  • Help with global navigation key-listener
    Help with global navigation key-listener

    Dec 26, 2019

    I'm trying to make an app with multiple routes navigable via left-right arrow keys. I've used keyboardist to add next() and prev() functions at the top level of my app, but I can't figure out how to pass the path for the next page into them... Any ideas on how this could work, perhaps specifying next-page as a prop on the route itself ?

    Reply
  • Typo fix
    Typo fix

    Jul 2, 2018

                                                                                                                                                                                                           
    Reply