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>
Arguments
keys
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!'))
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!');
break;
case 'b': alert('You pressed shift+b!');
break;
case 'r': alert('You pressed r!');
break;
case 'f': alert('You pressed f!');
break;
}
})
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!');
break;
case 'b': alert('You pressed shift+b!');
break;
case 'r': alert('You pressed r!');
break;
case 'f': alert('You pressed f!');
break;
}
})
Also you could work directly with hotkey trigger:
const HOTKEYS = [
ACTION_A: 'ctrl+a',
ACTION_B: 'shift+b',
]
useHotkeys(HOTKEYS, (_, { hotkey }) => {
switch (hotkey) {
case HOTKEYS.ACTION_A: alert('You pressed ctrl+a!');
break;
case HOTKEYS.ACTION_B: alert('You pressed shift+b!');
break;
}
})
callback
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.
event
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.
handler
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 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.
options
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
};
Properties
enabled
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
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:
INPUT
, TEXTAREA
, SELECT
ignoreEventWhen
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) => {
return e.target.className.includes('special-element')
},
})
enabled
enabled: boolean // default: true
Setting this to false
prevents the hook from doing anything.
splitKey
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.
scopes
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
keyup: boolean // default: false
Set this to true
if we want the hook to trigger our callback on the browsers keyUp
event.
keydown
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.
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
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,
});
document
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 (
<FrameComponent>
<InsideFrameComponent/>
</FrameComponent>
)
}
ignoreModifiers
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 '/'" />
)
}
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
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.
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.
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]);