[]()
[](https://travis-ci.org/greena13/react-hotkeys)
[](https://github.com/greena13/react-hotkeys/blob/master/LICENSE)
[](https://codeclimate.com/github/greena13/react-hotkeys/maintainability)
[](https://gitter.im/chrisui/react-hotkeys)
A declarative library for handling hotkeys and focus areas in React applications.
### Upgrading from 1.\*.\* ?
See the [upgrade notes](https://github.com/greena13/react-hotkeys/releases/tag/v2.0.0).
## Looking for new maintainers
This package has been more or less unmaintained for over 6 months and is search of new maintainers to help address the many outstanding issues. If you are interested in helping out in any capacity, please [get in touch](https://github.com/greena13/react-hotkeys/issues/247).
## Feature Overview
- Minimal declarative [JSX](#HotKeys-component-API) and [HoC](#withHotKeys-HoC-API) APIs
- [Browser key names](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) and [Mousetrap syntax](https://github.com/ccampbell/mousetrap)
- Define [global](#GlobalHotKeys-component) and [in-focus](#HotKeys-component) hot keys
- [Display a list of available hot keys to the user](#Displaying-a-list-of-available-hot-keys)
- [Define custom key codes](#defining-custom-key-codes) for WebOS and other environments
- Allow users to [set their own keyboard shortcuts](#setting-dynamic-hotkeys-at-runtime)
- Works with React's Synthetic KeyboardEvents and event delegation and provides [predictable and expected behaviour](#Interaction-with-React) to anyone familiar with React
- Optimized by default, but allows you to turn off different optimisation measures in a granular fashion
- Customizable through a simple [configuration API](#Configuration)
- [Optimized for larger applications](#Optimizations), with many hot keys active at once
- Depends only on `prop-types` and a peer dependency of `react`
- Uses rollup and Uglify and strips out comments and logging for a small production build
- More than [2000 automated tests](https://github.com/greena13/react-hotkeys/tree/master/test)
## Basic Usage
#### Define a key map
```javascript
import { HotKeys } from "react-hotkeys";
import MyNode from "./MyNode";
import React from 'react';
const keyMap = {
SNAP_LEFT: "command+left",
DELETE_NODE: ["del", "backspace"]
};
const App = () => {
return (
);
};
export default App;
```
#### Define handlers
```javascript
import { HotKeys } from "react-hotkeys";
import React from 'react';
const MyNode = () => {
const deleteNode = React.useCallback(() => {
// logic here
}, [])
const handlers = {
DELETE_NODE: deleteNode
};
return Node contents;
};
export default MyNode;
```
## Contents
- [Licenses](#licenses)
- [Support](#support)
- [Install](#install)
- [CommonJS & ES6 Modules](#commonjs--es6-modules)
- [The latest pre-release](#the-latest-pre-release)
- [UMD](#umd)
- [Development build](#development-build)
- [Minified production build](#minified-production-build)
- [Bower](#bower)
- [Defining key maps](#defining-key-maps)
- [Key Combinations vs Sequences](#key-combinations-vs-sequences)
- [Full Reference](#full-reference)
- [Alternative Hotkeys](#alternative-hotkeys)
- [Specifying key events (keydown, keypress, keyup)](#specifying-key-events-keydown-keypress-keyup)
- [Specifying key map display data](#specifying-key-map-display-data)
- [Deciding which key map syntax to use](#deciding-which-key-map-syntax-to-use)
- [Defining custom key codes](#defining-custom-key-codes)
- [Setting dynamic hotkeys at runtime](#setting-dynamic-hotkeys-at-runtime)
- [Defining Handlers](#defining-handlers)
- [Interaction with React](#interaction-with-react)
- [HotKeys components](#hotkeys-components)
- [HotKeys component API](#hotkeys-component-api)
- [withHotKeys HoC API](#withhotkeys-hoc-api)
- [Simple use-case](#simple-use-case)
- [Pre-defining default prop values](#pre-defining-default-prop-values)
- [GlobalHotKeys component](#globalhotkeys-component)
- [GlobalHotKeys component API](#globalhotkeys-component-api)
- [How actions are resolved](#how-actions-are-resolved)
- [How nested key maps are matched](#how-nested-key-maps-are-matched)
- [How combinations and sequences are matched](#how-combinations-and-sequences-are-matched)
- [How action handlers are resolved](#how-action-handlers-are-resolved)
- [Displaying a list of available hot keys](#displaying-a-list-of-available-hot-keys)
- [Allowing hotkeys and handlers props to change](#allowing-hotkeys-and-handlers-props-to-change)
- [Ignoring events](#ignoring-events)
- [What it actually means to ignore an event](#what-it-actually-means-to-ignore-an-event)
- [IgnoreKeys component](#ignorekeys-component)
- [IgnoreKeys component API](#ignorekeys-component-api)
- [withHotKeysIgnore HoC API](#withhotkeysignore-hoc-api)
- [ObserveKeys component](#observekeys-component)
- [ObserveKeys component API](#observekeys-component-api)
- [withObserveKeys HoC API](#withobservekeys-hoc-api)
- [Configuration](#configuration)
- [Logging](#logging)
- [Optimizations](#optimizations)
- [Code optimizations](#code-optimizations)
- [Production optimizations](#production-optimizations)
- [Managing focus in the browser](#managing-focus-in-the-browser)
- [Focusable elements](#focusable-elements)
- [Tab Index](#tab-index)
- [Autofocus](#autofocus)
- [Programmatically manage focus](#programmatically-manage-focus)
- [Get the element currently in focus](#get-the-element-currently-in-focus)
- [Preventing default browser behaviour](#preventing-default-browser-behaviour)
- [Troubleshooting & Gotchas](#troubleshooting--gotchas)
- [Hotkeys is wrapping my components in a div that is breaking my styling](#hotkeys-is-wrapping-my-components-in-a-div-that-is-breaking-my-styling)
- [Other keyboard event listeners are no longer being triggered](#other-keyboard-event-listeners-are-no-longer-being-triggered)
- [Actions aren't being triggered when using withHotKeys](#actions-arent-being-triggered-when-using-withhotkeys)
- [Actions aren't being triggered for HotKeys](#actions-arent-being-triggered-for-hotkeys)
- [React Hotkeys thinks I'm holding down a key I've released](#react-hotkeys-thinks-im-holding-down-a-key-ive-released)
- [Blue border appears around children of HotKeys](#blue-border-appears-around-children-of-hotkeys)
- [Stability & Maintenance](#stability--maintenance)
- [Contribute, please!](#contribute-please)
- [Using GitHub Issues](#using-github-issues)
- [Submitting a Pull Request](#submitting-a-pull-request)
- [Commit messages](#commit-messages)
- [Build notes](#build-notes)
- [Build scripts](#build-scripts)
- [Development builds](#development-builds)
- [Production builds](#production-builds)
- [Build configuration](#build-configuration)
- [Authorship](#authorship)
## Licenses
`react-hotkeys` is released under the [ISC License](/LICENSE).
However, please note: the source code found in the lib/vendor directory is under the MIT License - please see the license file for each directory for more information.
## Support
If you use React Hotkeys and it has saved you time or money, please consider contributing. You will be supporting `react-hotkeys` by supporting its maintainer.
Please see my [Patreon Page](https://www.patreon.com/aleckgreenham) for details of why your support is needed, and how it will be used.
For recurring and publicly acknowledged support:
| Payment Option | Link/Address |
|:---|:----|
| Patreon | https://www.patreon.com/aleckgreenham |
For one-off or irregular contributions:
| Payment Option | Link/Address |
|:---|:----|
| Paypal | https://www.paypal.me/aleckgreenham |
| Bitcoin | 1ETTdVEahUqBaGXRQaiEgMhVjYQU1jQKKT |
| Ethereum | 0x6C9F9879f684e84314f5810f8F196bdB2c4e15c0 |
| Stellar | GBJ5T7V7YVRN4D2PBZTOCWREJXYWWGNDIQGRFDGQAZNTYYIW7BMWDRYA |
## Install
### CommonJS & ES6 Modules
`react-hotkeys` is available as a CommonJS or a ES6 Modules through npm or yarn. It uses `NODE_ENV` to determine whether to export the development or production build in your library or application.
It is expected you will use a bundling tool like Webpack or Uglify to remove the version of the bundle you are not using with each version of your application's code, to keep the library size to a minimum.
#### The latest pre-release
##### npm
```
npm install react-hotkeys@next --save
```
##### yarn
```
yarn add react-hotkeys@next
```
#### Latest stable release
##### npm
```
npm install react-hotkeys --save
```
##### yarn
```
yarn add react-hotkeys
```
### UMD
`react-hotkeys` as a UMD module is available on your CDN of choice.
Change `1.0.1` for the version that you would like to use.
#### Development build
```
```
```
```
#### Minified production build
```
```
```
```
### Bower
Bower support was removed in `v1.0.0`, but those who already rely on earlier versions of `react-hotkeys` through Bower can continue to do so using the following command:
```
bower install react-hotkeys@0.10.0
```
The Bower version of the package will **not** be supported going forward (including fixing any outstanding issues).
## Defining key maps
`react-hotkeys` uses key maps to decouple defining keyboard shortcuts from the functions they call. This allows hot keys and handler functions to be defined and maintained independent of one another.
> When a user presses the corresponding combination or sequence of keys, it is said they _match_ the hot keys, which causes an action to be _triggered_. `react-hotkeys` may then resolve an appropriate handler function to _handle_ the action.
Key maps are Plain Old JavaScript Objects, where the keys are the action names and the values are usually a [Mousetrap-supported](https://craig.is/killing/mice) or [Browser Key Values](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) sequence string (but can also be an [array](#Alternative-Hotkeys) or an [object](#Key-Combination-vs-Sequences)) that must be matched in order to trigger the action.
```javascript
const keyMap = {
deleteNode: "del",
moveUp: "up"
};
```
#### Key Combinations vs Sequences
Every hotkey or sequence string is parsed and treated as a sequence of key combinations. The simplest case is a sequence of 1 key combination, consisting of 1 key: e.g. `'a'` or `'shift'`.
```
// Key sequence with a combination of a single key
'4'
// Special single key sequence (ie. shift is handled automagically)
'?'
// Sequence of a single combination with multiple keys (keys must be pressed at the same time)
'command+shift+k'
// Sequence of multiple combinations (keys must be pressed and released one after another)
'up down left right'
```
#### Full Reference
Please refer to [Mousetrap's documentation](https://craig.is/killing/mice) or [Browser Key Values](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) for an exhaustive list of supported shortcuts and sequences.
#### Alternative Hotkeys
You can specify multiple _alternative_ key sequences (they will trigger the same action) using arrays:
```javascript
const keyMap = {
DELETE_NODE: ["del", "backspace"],
MOVE_UP: ["up", "w"]
};
```
#### Specifying key events (keydown, keypress, keyup)
By default, `react-hotkeys` will match hotkey sequences on the `keydown` event (or, more precisely: on the `keydown` event of the last key to complete the last combination in a sequence).
If you want to trigger a single action on a different key event, you can use the object syntax and the `action` attribute to explicitly set which key event you wish to bind to:
```javascript
const keyMap = {
CONTRACT: "alt+down",
COMMAND_DOWN: { sequence: "command", action: "keydown" }
};
```
If you want to change the default key event for all hotkeys, you can use the `defaultKeyEvent` option of the [configuration API](#Configuration).
The full list of valid key events is: `keypress`, `keydown`, and `keyup`.
#### Specifying key map display data
You can specify data used to display the application's key maps using the object syntax and the `name`, `description` and `group` attributes (each are optional):
```javascript
{
SHOW_DIALOG: {
name: 'Display keyboard shortcuts',
sequence: 'shift+?',
action: 'keyup'
}
}
```
If you want to also provide alternative key sequences for the same action, use the `sequences` attribute:
```javascript
{
SHOW_DIALOG: {
name: 'Display keyboard shortcuts',
sequences: ['shift+?', { sequence: '`', action: 'keyup' }],
action: 'keyup'
}
}
```
#### Deciding which key map syntax to use
As a general rule, you should use the syntax that is the most brief, but still allows you to express the configuration you want.
| Syntax Type | Use when you ... |
| :--------------------------- | :------------------------------------------------------------------------------------------------------------------ |
| String | Have a single key sequence and don't have any special requirements (Default case) |
| Array of strings | Need alternative key maps that trigger the same action, and are happy with them triggering on the default key event |
| Array of objects | Need alternative key maps that trigger the same action, and want to them to trigger on a different key event |
| Object | Have a single key sequence and want to specify a different key event or display data |
| Object (sequences attribute) | Have multiple key sequences that trigger the same action, and want to specify a different key event or display data |
#### Defining custom key codes
When you are working in a WebOS environment, or a similar, you may have need to define custom key codes. You can do so using the `customKeyCodes` [Configuration option](#configuration):
```javascript
import {configure} from 'react-hotkeys';
configure({
customKeyCodes: {
10009: 'BackTV'
}
})
```
Once defined, you are then able to use the key names in you action sequences:
```javascript
const keyMap = {
MY_ACTION: 'BackTV',
};
```
#### Setting dynamic hotkeys at runtime
`react-hotkeys` has basic support for setting dynamic hotkeys - i.e. letting the user set their own keyboard shortcuts at runtime. Once you have set up the necessary UI for [viewing the current keyboard shortcuts](#displaying-a-list-of-available-hot-keys) (and opting to change them), you can then use the `recordKeyCombination` function to capture the keys the user wishes to use.
`recordKeyCombination` accepts a callback function that will be called on the last `keyup` of the next key combination - immediately after the user has pressed the key combination they wish to assign. The callback then unbinds itself, so you do not have to worry about tidying up after it.
`recordKeyCombination` returns a function you can call at any time after binding the listener, to cancel listening without waiting for the key combination to complete.
The callback function receives a single argument with the following schema:
```javascript
{
/**
* Combination ID that can be passed to the keyMap prop to (re)define an
* action's key sequence
*/
id: '',
/**
* Dictionary of keys involved in the combination
*/
keys: { keyName: true }
}
// Example:
{
id: 'a',
keys: { a: true }
}
```
If you are updating hotkeys without changing focus or remounting the component that defines them, you will need to make sure you use the [`allowChanges` prop](#hotkeys-component-api) to ensure the new keymaps are honoured immediately.
An example, rendering two dialogs:
* One for displaying the application's key maps using the [getApplicationKeyMap](#displaying-a-list-of-available-hot-keys) function
* Another for telling the user when to press the keys they want to bind to an action, meanwhile listening with `recordKeyCombination()`
```javascript
import { recordKeyCombination } from 'react-hotkeys';
import React from 'react';
renderDialog(){
if (this.state.showShortcutsDialog) {
const keyMap = getApplicationKeyMap();
return (
);
}
}
showChangeShortcutDialog(actionName) {
const cancelListening = recordKeyCombination(({id}) => {
this.setState({
showShortcutsDialog: true,
changingActionShortcut: null,
keyMap: {
...this.state.keyMap,
[actionName]: id
}
});
});
this.setState({
showShortcutsDialog: false,
changingActionShortcut: {
cancel: () => {
cancelListening();
this.setState({
showShortcutsDialog: true,
changingActionShortcut: null
});
}
}
});
}
```
## Defining Handlers
Key maps trigger actions when they match a key sequence. Handlers are the functions that `react-hotkeys` calls to handle those actions.
Handlers may be defined in the same `` component as the key map:
```javascript
import { HotKeys } from "react-hotkeys";
import React from 'react';
const keyMap = {
MOVE_UP: "up"
};
const handlers = {
MOVE_UP: event => console.log("Move up hotkey called!")
};
;
```
Or they may be defined in any _descendant_ of the `` component that defines the key map:
```javascript
import { HotKeys } from "react-hotkeys";
import React from 'react';
const keyMap = {
MOVE_UP: "up"
};
const handlers = {
MOVE_UP: event => console.log("Move up hotkey called!")
};
;
```
## Interaction with React
Rather than re-invent the wheel, `react-hotkeys` piggy-backs of the React SyntheticEvent and event propagation, so all of the normal React behaviour that you expect still applies.
- Key events propagate up from a source or target towards the root of the application.
- If an event has `stopPropagation()` called on it, it will not be seen by components higher up in the render tree.
## HotKeys components
`` components listen only to key events that happen when one of their DOM-mounted descendents are in focus (``, ``, ``, etc). This emulates (and re-uses) the behaviour of the browser and React's SyntheticEvent propagation.
This is the default type of `` component, and should normally be your first choice for efficiency and clarity (the user generally expects keyboard input to affect the focused element in the browser).
## HotKeys component API
The HotKeys component provides a declarative and native JSX syntax that is best for succinctly declaring hotkeys in a way that best maintains separation and encapsulation with regards to the rest of your code base.
However, it [does require that its children be wrapped in a DOM-mounted node](#hotkeys-is-wrapping-my-components-in-a-div-that-is-breaking-my-styling), which can break styling and add extra levels to your render tree.
```javascript
/**
* Wraps all children in a DOM-mountable component
*/
{ children }
```
## withHotKeys HoC API
The HotKeys component API is generally recommended, but if wrapping your component in a DOM-mountable node is not acceptable, or you need more control over how the `react-hotkeys` props are applied, then the `withHotKeys()` HoC is available.
### Simple use-case
The simplest use-case of `withHotKeys()` is to simply pass it your component class as the first argument. What is returned is a new component that will accept all of the same props as a `` component, so you can specify key maps and handlers at render time, for example.
> The component you wrap **must** take responsibility for passing the `hotKeys` props to a DOM-mountable element. If you fail to do this, key events will not be detected when a descendant of the component is in focus.
```javascript
import { withHotKeys } from "react-hotkeys";
class MyComponent extends Component {
render() {
/**
* Must unwrap hotKeys prop and pass its values to a DOM-mountable
* element (like the div below).
*/
const { hotKeys, ...remainingProps } = this.props;
return (
My HotKeys are effective here
{this.props.children}
;
```
### Pre-defining default prop values
You can use the second argument of `withHotKeys` to specify default values for any props you would normally pass to ``. This means you do not have to specify them at render-time.
> If you do provide prop values when you render the component, these will be merged with (and override) those defined in the second argument of `withHotKeys`.
```javascript
import { withHotKeys } from "react-hotkeys";
import React from 'react';
class MyComponent extends Component {
render() {
/**
* Must unwrap hotKeys prop and pass its values to a DOM-mountable
* element (like the div below).
*/
const { hotKeys, ...remainingProps } = this.props;
return (
My HotKeys are effective here
{this.props.children}
;
```
## GlobalHotKeys component
`` components match key events that occur anywhere in the document (even when no part of your React application is in focus).
```javascript
const keyMap = { SHOW_ALL_HOTKEYS: "shift+?" };
const handlers = { SHOW_ALL_HOTKEYS: this.showHotKeysDialog };
;
```
`` generally have no need for children, so should use a self-closing tag (as shown above). The only exception is when you are nesting other `` components somewhere in the descendents (these are mounted before their parents, and so are generally matched first).
## GlobalHotKeys component API
The GlobalHotKeys component provides a declarative and native JSX syntax for defining hotkeys that are applicable beyond you React application.
```javascript
/** * Wraps all children in a DOM-mountable component */
{children}
```
## How actions are resolved
### How nested key maps are matched
For keymaps defined with `` components, how close your `` component is to the element currently focused in the DOM has the greatest affect on how actions are resolved. Whenever a key event occurs (`keydown`, `keypress` or `keyup`), `react-hotkeys` starts at the `` component closest to the event's target (the focused element in the browser) and searches up through the hierarchy of focused `` components, examining each `keyMap` for actions for which the current key completes the specified combination or sequence.
Regardless of where `` components appear in the render tree, they are matched with key events after the event has finished propagating through the React app (if the event originated in the React at all). This means if your React app is in focus and it handles a key event, it will be ignored by the `` components.
The order used for resolving actions and handlers amongst `` components, is the order in which they mounted (those mounted first, are given the chance to handle an action first). When a `` component is unmounted, it is removed from consideration. This can get less deterministic over the course of a long session using a React app as components mount and unmount, so it is best to define actions and handlers that are globally unique.
It is recommended to use `` components whenever possible for better performance and reliability.
> You can use the [autofocus attributes](#Autofocus) or [programmatically manage focus](#Programmatically-manage-focus) to automatically focus your React app so the user doesn't have to select it in order for hot keys to take effect. It is common practice to place a `` component towards the top of your application to match hot keys across your entire React application.
### How combinations and sequences are matched
For key combinations, the action only matches if the key is the last one needed to complete the combination. For sequences, the action matches for the last key to complete the last combination in the sequence.
By default, sub-matches are disabled so if you have two actions bound to `cmd+a` and `a`, and you press the `cmd` key and then the `a` key (without releasing the `cmd` key), then the `cmd+a` combination is matched. This allows you to define longer, application-wide key combinations at the top of your app, without them being hidden by shorter context-dependent combinations in different parts of your app. However, it does depend on the order the keys are pressed: in the above example, if `a` was pressed first and then `cmd`, the `a` action would be matched. The trade-off for this behaviour is that combinations are not permitted to overlap: if you have two actions bound to `a` and `b` and the user presses `a` and then `b` without first releasing `a`, only the action associated with `a` will be called (because there are no actions associated with `a+b`). If you want allow sub-matches, you can use the [`allowCombinationSubmatches` configuration option](#configuration).
The match occurs on the key event you have specified when defining your keymap (the default is `keydown` if you have not overridden the [`defaultKeyEvent` configuration option](#configuration)).
Once a matching action is found, `react-hotkeys` then searches for the corresponding action handler.
## How action handlers are resolved
> If one of the DOM-mounted descendents of an `` component are in focus (and it is listening to key events) AND those key events match a hot key in the component's key map, then the corresponding action is triggered.
`react-hotkeys` starts at the `` component closest to the event's target (the element that was in focus when the key was pressed) and works its way up through the component tree of focused `` components, looking for a matching handler for the action. The handler closest to the event target AND a descendant of the `` component that defines the action (or the component itself), is the one that is called.
That is:
- Unless one of the DOM-mounted descendents of a `` component is in focus, the component's actions are not matched
- Unless a `` component is nested within the `` component that defines the action (or is the same `` component), its handler is not called
- If a `` component closer to the event target has defined a handler for the same action, a `` component's handler won't be called (the closer component's handler will)
A more exhaustive enumeration of `react-hotkeys` behaviour can be found by reviewing the [test suite](https://github.com/greena13/react-hotkeys/tree/master/test).
## Displaying a list of available hot keys
`react-hotkeys` provides the `getApplicationKeyMap()` function for getting a mapping of all actions and key sequences that have been defined by components that are currently mounted.
They are returned as an object, with the action names as keys, and the values are objects describing the key map.
Regardless of which syntax you used to define the keymap, they always appear in the following format:
```
{
ACTION_NAME: {
/**
* Optional attributes - only present if you defined them
*/
name: 'name',
group: 'group',
description: 'description',
/**
* Attributes always present
* /
sequences: [
{
action: 'keydown',
sequence: 'alt+s'
},
// ...
]
},
// ...
}
```
Below is how the example application renders a dialog of all available hot keys:
```javascript
import { getApplicationKeyMap } from 'react-hotkeys';
import React from 'react';
// ...
renderDialog(){
if (this.state.showDialog) {
const keyMap = getApplicationKeyMap();
return (
);
}
}
```
## Allowing hotkeys and handlers props to change
For performance reasons, by default `react-hotkeys` takes the `keyMap` and `handlers` prop values when `` components are focused and when `` components are mounted. It ignores all subsequent updates
to their values when these props change.
If you need the ability to change them while `` are still in focus, or while `` are still mounted, then you can pass the `allowChanges` prop, permitting this behaviour for the particular component.
If you need to do this for all your `` and `` components, you can use the `ignoreKeymapAndHandlerChangesByDefault` option for the [Configuration API](#Configuration). This should normally never be done, as it can have significant performance implications.
## Ignoring events
By default, all key events that originate from ``, `