A declarative library for handling hotkeys and focus within a React application
npm install @vargentum/react-hotkeys
![npm]()



A declarative library for handling hotkeys and focus areas in React applications.
> 🚨 🚨 Warning: This Readme is for the latest pre-release. The documentation for the latest stable release is available here. 🚨 🚨
See the upgrade notes.
- Minimal declarative JSX and HoC APIs
- Browser key names and Mousetrap syntax
- Define global and in-focus hot keys
- Display a list of available hot keys to the user
- Works with React's Synthetic KeyboardEvents and event delegation and provides predictable and expected behaviour 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
- Optimized for larger applications, 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 1800 automated tests
#### Define a key map
``javascript
import { HotKeys } from "react-hotkeys";
import MyNode from "./MyNode";
const keyMap = {
SNAP_LEFT: "command+left",
DELETE_NODE: ["del", "backspace"]
};
const App = () => {
return (
);
};
export default App;
`
#### Define handlers
`javascript
import { HotKeys } from "react-hotkeys";
const MyNode = () => {
const handlers = {
DELETE_NODE: this.deleteNode
};
return
};
export default MyNode;
`
- Licenses
- Install
- CommonJS & ES6 Modules
- The latest pre-release
- UMD
- Development build
- Minified production build
- Bower
- Defining key maps
- Key Combinations vs Sequences
- Full Reference
- Alternative Hotkeys
- Specifying key events (keydown, keypress, keyup)
- Deciding which key map syntax to use
- Defining Handlers
- DEPRECATED: Hard Sequence Handlers
- Interaction with React
- HotKeys components
- How action handlers are resolved
- HotKeys component API
- withHotKeys HoC API
- Simple use-case
- Pre-defining default prop values
- GlobalHotKeys component
- How actions and handlers are resolved
- GlobalHotKeys component API
- Displaying a list of available hot keys
- Allowing hotkeys and handlers props to change
- Ignoring events
- What it actually means to ignore an event
- IgnoreKeys component
- IgnoreKeys component API
- withHotKeysIgnore HoC API
- ObserveKeys component
- ObserveKeys component API
- withObserveKeys HoC API
- Configuration
- Logging
- Optimizations
- Code optimizations
- Production optimizations
- Managing focus in the browser
- Focusable elements
- Tab Index
- Autofocus
- Programmatically manage focus
- Get the element currently in focus
- Preventing default browser behaviour
- Troubleshooting & Gotchas
- Hotkeys is wrapping my components in a div that is breaking my styling
- Other keyboard event listeners are no longer being triggered
- Actions aren't being triggered when using withHotKeys
- Actions aren't being triggered for HotKeys
- Blue border appears around children of HotKeys
- Support
- Stability & Maintenance
- Contribute, please!
- Using GitHub Issues
- Submitting a Pull Request
- Build notes
- Build scripts
- Development builds
- Production builds
- Build configuration
- Authorship
react-hotkeys is released under the ISC 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.
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
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 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).
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 or Browser Key Values sequence string (but can also be an array or an object) 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 or Browser 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.
The full list of valid key events is: keypress, keydown, and 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.
| Question | Yes | No |
| :---------------------------------------------------------------------------------------- | :---------------------------------- | :---------------------- |
| Need to define alternative key sequences to trigger the same action? | Use an array of strings or objects. | Use a string or object. |
| Need to explicitly define the key event to bind to (or some other additional option)? | Use an object. | Use a string. |
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";
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";
const keyMap = {
MOVE_UP: "up"
};
const handlers = {
MOVE_UP: event => console.log("Move up hotkey called!")
};
#### DEPRECATED: Hard Sequence Handlers
Handlers associated to actions with names that are valid key sequence strings implicitly define actions that are matched by the corresponding key sequence. This means you do not have to define the key maps in order for these handlers to "just work".
This functionality is not advised and exists mainly for backwards compatibility. It is generally advisable to explicitly define an action in a key map rather than rely on this behaviour.
To use hard sequence handlers, you must first enable them using the
enableHardSequences configuration option.`javascript
/**
* If no named 'up' action has been defined in a key map and it is a valid
* key sequence, react-hotkeys assumes it's a hard sequence handler and
* implicitly defines an action for it
*/const handlers = {
up: event => console.log("up key 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).$3
> 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.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, which can break styling and add extra levels to your render tree.
`javascript /**
* An object that defines actions as keys and key sequences as values
* (using either a string, array or object).
*
* Actions defined in one HotKeys component are available to be handled
* in an descendent HotKeys component.
*
* Optional.
*/
keyMap={ {} }
/**
* An object that defines handler functions as values, and the actions
* that they handle as keys.
*
* Optional.
*/
handlers={ {} }
/**
* The type of DOM-mountable component that should be used to wrap
* the component's children.
*/
component={ 'div' }
/**
* tabindex value to pass to DOM-mountable component wrapping children
*/
tabIndex={-1}
/**
* Whether the keyMap or handlers are permitted to change after the
* component mounts. If false, changes to the keyMap and handlers
* props will be ignored
*
* Optional.
*/
allowChanges={false}
/**
* A ref to add to the underlying DOM-mountable node. Pass a function
* to get a reference to the node, so you can call .focus() on it
*/
innerRef: {undefined}
>
/**
* 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.$3
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}
);
}
}const MyHotKeysComponent = withHotKeys(MyComponent);
const keyMap = {
TEST: "t"
};
const handlers = {
TEST: () => console.log("Test")
};
You can press 't' to log to the console.
;
`$3
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";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}
);
}
}const keyMap = {
TEST: "t"
};
const handlers = {
TEST: () => console.log("Test")
};
const MyHotKeysComponent = withHotKeys(MyComponent, { keyMap, handlers });
/**
* Render without having to specify prop values
*/
You can press 't' to log to the console.
;
`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).$3
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 or 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.GlobalHotKeys component API
The GlobalHotKeys component provides a declarative and native JSX syntax for defining hotkeys that are applicable beyond you React application.
`javascript
/**
* An object that defines actions as keys and key sequences as values
* (using either a string, array or object).
*
* Actions defined in one HotKeys component are available to be handled
* in an descendent HotKeys component.
*
* Optional.
*/
keyMap={{}}
/**
* An object that defines handler functions as values, and the actions
* that they handle as keys.
*
* Optional.
*/
handlers={{}}
/**
* Whether the keyMap or handlers are permitted to change after the
* component mounts. If false, changes to the keyMap and handlers
* props will be ignored
*
* Optional.
*/
allowChanges={false}
>
/* Wraps all children in a DOM-mountable component */
{children}
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 (it is up to you to decide how to translate them to be displayed) and arrays of key sequences that trigger them, as keys.
Below is how the example application renders a dialog of all available hot keys:
`javascript
import { getApplicationKeyMap } from 'react-hotkeys';// ...
renderDialog() {
if (this.state.showDialog) {
const keyMap = getApplicationKeyMap();
return (
Keyboard shortcuts
{ Object.keys(keyMap).map((actionName) => (
{ actionName.replace('_', ' ') }
{ keyMap[actionName].map((keySequence) => {keySequence}) }
)) }
);
}
}
`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. This should normally never be done, as it can have significant performance implications.Ignoring events
By default, all key events that originate from
, }
`
You can retrieve the element that is currently focused using the following:
`javascript`
document.activeElement;
If you find that you want to bind to a key sequence that is already used by the browser, you can prevent the default behaviour by calling the preventDefault method on the event object:
`javascript`
event.preventDefault();
It's generally not advised to do this, as it likely violates the Principle of Least Surprise.
#### Hotkeys is wrapping my components in a div that is breaking my styling
You have 3 options:
1. Use the component prop to specify a span or some other alternative DOM-mountable component to wrap your component in, each time you render a component you don't want to wrap in a div element.defaultComponent
1. Use the configuration option to specify a span or some other alternative DOM-mountable component to wrap _all_ children in.
1. Use the withHotKeys HoC API to avoid rendering a wrapping component at all.
#### Other keyboard event listeners are no longer being triggered
For improved performance, by default react-hotkeys calls stopPropagation() on all events that it handles. You can change this using the stopEventPropagationAfterHandling and stopEventPropagationAfterIgnoring configuration options.
#### Actions aren't being triggered when using withHotKeys
Check that you are correctly passing the hotKeys props to a DOM-mountable component.
#### Actions aren't being triggered for HotKeys
Make sure you are focusing a descendant of the component before you press the keys.
Check that the component that defines the handler is also an ancestor of the focused component, and is above (or _is_) the component that defines the handlers.
Also make sure your React application is not calling stopPropagation() on the key events before they reach the component that defines the keyMap.
Finally, make sure your key event are not coming from one of the tags ignored by react-hotkeys.
#### Blue border appears around children of HotKeys
react-hotkeys adds a
around its children with a tabindex="-1" to allow them to be programmatically focused. This can result in browsers rendering a blue outline around them to visually indicate that they are the elements in the document that is currently in focus.This can be disabled using CSS similar to the following:
`css
div[tabindex="-1"]:focus {
outline: 0;
}
`Support
Please use Gitter to ask any questions you may have regarding how to use
react-hotkeys.If you believe you have found a bug or have a feature request, please open an issue.
Stability & Maintenance
react-hotkeys is considered stable and already being widely used (most notably Lystable and Whatsapp).Contribute, please!
If you're interested in helping out with the maintenance of
react-hotkeys, make yourself known on Gitter, open an issue or create a pull request.All contributions are welcome and greatly appreciated - from contributors of all levels of experience.
Collaboration is loosely being coordinated across Gitter and Projects.
$3
- Use the search feature to check for an existing issue
- Include as much information as possible and provide any relevant resources (Eg. screenshots)
- For bug reports ensure you have a reproducible test case
- A pull request with a breaking test would be super preferable here but isn't required
$3
- Squash commits
- Lint your code with eslint (config provided)
- Include relevant test updates/additions
Build notes
react-hotkeys uses a mixture of build tools to create each of the development and production bundles, which can be confusing to navigate and understand.$3
All build commands are included in the
package.json:| Command | Description |
| :--------------------------- | :----------------------------------------------------------------------------------------- |
|
yarn prepublish | Build all bundles using babel and rollup |
| yarn build-cjs | Build the development and production CommonJS bundles using babel and rollup, respectively |
| yarn build-es | Build the development and production ES6 bundles using babel and rollup, respectively |
| yarn build-umd | Build the development and production UMD bundles using rollup |
| yarn build-development | Build the development CommonJS bundle using babel |
| yarn build-es-development | Build the development ES6 bundle using babel |
| yarn build-umd-development | Build the development ES6 bundle using rollup |
| yarn build-production | Build the production CommonJS bundle using rollup |
| yarn build-es-production | Build the production ES6 bundle using rollup |
| yarn build-umd-production | Build the production ES6 bundle using rollup |$3
| Bundle | Transpiled with | Modularized with | Output |
| :------- | :-------------- | :--------------- | :------------ |
| CommonJS | Babel | Babel | /cjs/index.js |
| UMD | Babel | Rollup | /umd/index.js |
| ES6 | Babel | Babel | /es/index.js |
$3
| Bundle | Transpiled with | Optimized with | Minified with | Output |
| :------- | :-------------- | :------------- | :------------ | :---------------------------------- |
| CommonJS | Babel | Rollup | Uglify | cjs/react-hotkeys.production.min.js |
| UMD | Babel | Rollup | Uglify | /umd/react-hotkeys.min.js |
| ES6 | Babel | Rollup | Babel-minify | /es/react-hotkeys.production.min.js |
$3
To understand the configuration for any one build, you need to consult 3 places:
- The CLI arguments used in the
scripts of package.json
- The .babelrc file (match the env to the BABEL_ENV value set in scripts above)
- The rollup.configs.js (if applicable)Authorship
All credit, and many thanks, goes to Chris Pearce for the inception of
react-hotkeys and all versions before 1.0.0`.