Skip to main content
Version: 4.0

useHotkeys API

Function signature:

function useHotkeys<T extends Element>(
keys: string | string[],
callback: (event: KeyboardEvent, handler: HotkeysEvent) => void,
options: Options = {},
deps: any[] = []
): React.MutableRef<T | null>



keys: string | string[]

Set the keystrokes we want the hook to listen to. We can use single or multiple keys, modifier combinations, arrow keys, function keys, etc.

Listening to all keys

useHotkeys('*', (_, handler) => alert(handler.key))

Using modifiers

useHotkeys('ctrl+s, shift+w', () => alert('We\'re using modifiers now!'))

Using F keys

useHotkeys('f5', () => alert('F5 was pressed'))

Using multiple keys

useHotkeys('w, a, s, d', () => alert('Player moved!'))
Differentiating between multiple possible keys

If we use a combination of possible keys that use the same hook, we can use handler.keys to check which key the user pressed.

useHotkeys('ctrl+a, shift+b, r, f', (_, handler) => {
switch (handler.keys.join('')) {
case 'a': alert('You pressed ctrl+a!');
case 'b': alert('You pressed shift+b!');
case 'r': alert('You pressed r!');
case 'f': alert('You pressed f!');

This can also be an array:

useHotkeys(['ctrl+a', 'shift+b', 'r', 'f'], (_, handler) => {
switch (handler.keys.join('')) {
case 'a': alert('You pressed ctrl+a!');
case 'b': alert('You pressed shift+b!');
case 'r': alert('You pressed r!');
case 'f': alert('You pressed f!');


callback: (event: KeyboardEvent, handler: HotkeysEvent) => void

Gets executed when the defined keystroke gets hit by the user. event holds the browsers keyboard event, handler passes some additional information to handle the pressed key.


The browsers native keyboard event that gets created when the user hits a key. For a thorough documentation of this event check out the MDN Web Docs.


The handler holds information about the pressed key. In general, we should only need this object to handle our keyboard events. The most important property of the handler object is the keys prop:

  • keys: string[] - This will hold the pressed keystroke. So if we use multiple possible keystroke combinations for the same callback we can use this property to check which specific keystroke was pressed.

There are more properties attached to the handle that currently don't get populated with values. So we can safely ignore those.

The callback gets memoised

The callback we pass into the hook gets memoised, so every variable we reference inside the callback must be added to the dependencies array, otherwise we will get stale values. For more on memoisation in the context of React hooks read this nice article.


We can extensively configure how the hook behaves by passing it an options object. Below are all properties that the object takes.

// Default values
const options = {
enabled: true,
enableOnFormTags: false,
enableOnContentEditable: false,
combinationKey: '+',
splitKey: ',',
scopes: '*',
keyup: undefined,
keydown: true,
preventDefault: false,
description: undefined,
document: undefined,
ignoreModifiers: false,
// Type Definitions
type Trigger = boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean)
type FormTags = 'input' | 'textarea' | 'select' | 'INPUT' | 'TEXTAREA' | 'SELECT';

type Options = {
enabled?: Trigger
enableOnFormTags?: FormTags[] | boolean
enableOnContentEditable?: boolean
combinationKey?: string
splitKey?: string
scopes?: string | string[]
keyup?: boolean
keydown?: boolean
preventDefault?: Trigger
description?: string
document?: Document
ignoreModifiers?: boolean


enabled: boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean) // default: true

Determines if the callback should get triggered. Return false to prevent the execution of the callback and true to allow the callback to be triggered. You can also pass a function that returns a boolean.

enableOnFormTags: string[] // default: undefined

Normally we do not want a hotkey being triggered while a user types something into an input field. In some cases however this might desirable. We can enable the callback trigger for an input tag using the following values:


ignoreEventWhen: (e: KeyboardEvent) => boolean // default: undefined

Provides a fine control over what events to ignore. Can be used in special cases, for example

useHotkeys('a', someCallback, {
ignoreEventWhen: (e) => {
enabled: boolean // default: true

Setting this to false prevents the hook from doing anything.

splitKey: string // default: "+"

Specifies the key that is used to combine multiple hotkeys into keystrokes. The default value is +, so shift+a triggers when the user presses the "shift" key and the "a" key.


To group your hotkeys into different scopes, you can pass a string or an array of strings to the scopes property. This way you can easily enable or disable a group of hotkeys at once. More on this in the Grouping Hotkeys together section. By default all hotkeys are assigned to the wildcard * scope.

keyup: boolean // default: false

Set this to true if we want the hook to trigger our callback on the browsers keyUp event.

keydown: boolean // default: true

Set this to true if we want the hook to trigger our callback on the browsers keyDown event. This is the default behavior.

Setting keydown and keyup

If we set keyup to true and don't set the keydown prop (leaving the default), React Hotkeys Hook will assume that we want to only listen to the browsers keyUp event.

If we in fact want the callback to get triggered by both events, we have to explicitly set both properties like so:

useHotkeys('a', () => someCallback, {
keydown: true,
keyup: true
preventDefault: boolean | ((keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent) => boolean) // default: false

This flag determines if the default browser behavior should be prevented. false is the default value, so the browser will proceed with its default behavior. Setting this to true will prevent some of the default browser behavior.

A good example for this behavior is the override of meta+s, which normally triggers a save page dialog inside the browser.

useHotkeys('meta+s', someCallback, {
// This will prevent the browser from showing the save page dialog
preventDefault: true,

if our React app uses iframes, we can pass the document object of the iframe to the hook. This way the hook will bind the hotkeys to the iframe instead of the main document.

import FrameComponent from 'react-frame-component'

const InsideFrameComponent = () => {
const { document } = useFrame()

useHotkeys("s", () => console.log("I am triggered inside an iframe"), { document })

return <div>....</div>

function App() {
return (
ignoreModifiers: boolean // default: false

When listening to keystrokes, we can ignore the modifier keys (e.g. shift, alt, ctrl, meta) by setting this option to true. This is especially useful when we want to to listen to secondary key combinations like shift+1 (producing the exclamation point !). A common use case for this would be to listen to the / Character in order to focus a search input field.

function App() {
useHotkeys('/', () => inputRef.current?.focus(), { ignoreModifiers: true, preventDefault: true })

const inputRef = useRef<HTMLInputElement>(null)

return (
<input type='text' ref={inputRef} placeholder="search via '/'" />
Why is this necessary?

There are tons of keyboard layouts all across different languages and operating systems. For example, we reach the hashtag sign # via Shift+3 on a US keyboard, but on a German keyboard it has its own dedicated key. On the other hand in a German keyboard layout we reach the character [ via Option+5 on macOS, but on windows there is a dedicated key for that.

This is already confusing, and it gets even worse when we consider that users can customize their keyboard layout. So listening to something like # can or cannot involve a shift modifier.

But there are also two different scenarios possible from our apps view: We could say that we want to listen to the # character, no matter if the user presses Shift+3 or its own dedicated key. Here we can ignore any potential modifiers, so we set ignoreModifiers: true. But we could also say that we only want to listen to Shift+3, no matter the produced character. In this case we do need to listen to the modifier.

With this option, react-hotkeys-hook supports both ways.


deps: any[] // default: []

The dependency array lets us use the hook just like Reacts internal useCallback or useMemo hook. This is where our dependencies of the callback live. If for example our callback actions depend on a referentially unstable value or a value that will change over time, we should add this value to our deps array. Check out the documentation part for examples.

Return value

React.MutableRef<T | null>

The useHotkeys hook returns a React ref. This ref by default holds the value of null. We can use this ref to only trigger the hotkeys if a specific element has been focused by the user.

Live Editor
Not every html tag is able to receive focus by default

Elements that don't provide any native interactivity like <div>, <span>, <p>, etc. cannot receive a focus by default. If we want to use <div> tags instead of the <button> tags in the example above we have to provide a tabIndex prop to the tag. This way the focusing will work with all tags.

Live Editor

Function signature overloads

There is a common case where we want to pass dependencies to the hook but no options object. Normally we would need to write that out like this:

useHotkeys('a', () => someDependency, undefined, [someDependency]);

To streamline this use case the hook accepts function overloads. With this we can pass a dependency array as the third argument instead of the fourth.

function useHotkeys<T extends Element>(
keys: string,
callback: (event: KeyboardEvent, handler: HotkeysEvent) => void,
deps: any[] = []
): React.MutableRef<T | null>

So we are able to use the hook like this:

useHotkeys('a', () => someDependency, [someDependency]);