Full shortcut solution for react app.
-
Strict/Loose mode.
-
Page scoped register.
-
Dynamic register shortcut.
-
Dynamic enable/disable shortcut registered.
-
Flexible normal key combinations.
-
Use modern browser API.
-
Full types supported.
-
Shortcut validation.
# npm
npm install react-use-shortcuts
# yarn
yarn add react-use-shortcuts
# pnpm
pnpm add react-use-shortcuts
Key | Alias | Notes |
---|---|---|
ControlLeft |
Ctrl Control ControlOrCommand |
|
ControlRight |
Ctrl Control ControlOrCommand |
|
MetaLeft |
Command ConmandLeft ControlOrCommand |
Windows on Windows, Command on MacOS |
MetaRight |
Command ConmandRight ControlOrCommand |
Windows on Windows, Command on MacOS |
ShiftLeft |
Shift |
|
ShiftRight |
Shift |
|
AltLeft |
Option OptionLeft |
Option is only available on MacOS. |
AltRight |
Option OptionRight |
Option is only available on MacOS. |
Key | Notes |
---|---|
0 ~ 9 |
Number keys on keyboard main area or numpad area. |
a ~ z |
Alphabet keys |
F1 ~F12 |
Function keys |
, |
Comma |
. |
Period or Decimal on numpad |
/ |
Slash |
; |
Semicolon |
' |
Quote |
[ |
BracketLeft |
] |
BracketRight |
\ |
Backslash |
` |
Backquote |
Escape |
Alias Esc |
- |
Minus |
= |
Equal |
+ |
Add on numpad. not Shift+= |
* |
Multiple on numpad. not Shift+8 |
Backspace |
Backspace |
Delete |
Alias Del |
Tab |
Tab |
CapsLock |
Capslock |
Enter |
Enter or Enter on numpad. |
ArrowUp |
ArrowUp |
ArrowDown |
ArrowDown |
ArrowLeft |
ArrowLeft |
ArrowRight |
ArrowRight |
Insert |
Insert |
Home |
Home |
End |
End |
PageUp |
PageUp |
PageDown |
PageDown |
Space |
Space |
import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { registerShortcut, unregisterShortcut } = useShortcut();
// RegisterShortcut should be invoked in useEffect.
useEffect(() => {
registerShortcut('a', (event) => {
// event is the latest emitted keydown event.
// you can invoke preventDefault to prevent browser default behavior.
event.preventDefault();
// invoked whenever key A pressed.
console.log('You pressed A');
});
return () => {
unregisterShortcut('Ctrl+a');
};
}, []);
return <h1>Hello World!</h1>;
}
import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { registerShortcut, unregisterShortcut } = useShortcut();
useEffect(() => {
registerShortcut('Ctrl+a', (event) => {
// invoked whenever key Control and key A pressed.
console.log('You pressed Ctrl and A');
});
return () => {
unregisterShortcut('Ctrl+a');
};
}, []);
return <h1>Hello World!</h1>;
}
import React, { useEffect, useRef } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
const scope1 = useRef<HTMLDivElement>(null);
const scope2 = useRef<HTMLDivElement>(null);
return (
<div id="root">
<ReactShortcutProvider options={{ scope: scope1 }}>
<div ref={scope1} tabIndex={-1}>
<Main />
</div>
</ReactShortcutProvider>
<ReactShortcutProvider options={{ scope: scope2 }}>
<div ref={scope2} tabIndex={-1}>
<Main />
</div>
<Main />
</ReactShortcutProvider>
</div>
);
}
function Main() {
const { registerShortcut, unregisterShortcut } = useShortcut();
useEffect(() => {
registerShortcut('Ctrl+a', (event) => {
// invoked whenever key Control and key A pressed.
console.log('You pressed Ctrl and A');
});
return () => {
unregisterShortcut('Ctrl+a');
};
}, []);
return <h1>Hello World!</h1>;
}
react-use-shortcuts
work in strict mode by default, if you want to enable loose mode, you can set strict
to false. it is only affect the getCurrentKeyPressed
API.
import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider options={{ strict: false }}>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { getElement, getCurrentKeyPressed } = useShortcut();
useEffect(() => {
const cb = () => {
// if you pressed ControlLeft and A.
// print ControlLeft+a in strict mode.
// print Control+a in loose mode.
console.log(getCurrentKeyPressed());
};
getElement()!.addEventListener('keydown', cb);
return () => {
getElement()!.removeEventListener('keydown', cb);
};
}, []);
return <h1>Hello World!</h1>;
}
import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { registerShortcut, unregisterShortcut } = useShortcut();
// RegisterShortcut should be invoked in useEffect.
useEffect(() => {
registerShortcut('a+b', (event) => {
// invoked whenever key A pressed.
console.log('You pressed A and B');
});
registerShortcut('a+a', (event) => {
// invoked whenever key A pressed twice.
console.log('You pressed A twice');
});
return () => {
unregisterShortcut('a+b');
unregisterShortcut('a+a');
};
}, []);
return <h1>Hello World!</h1>;
}
import React, { useEffect, useCallback, useState } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { registerShortcut, unregisterShortcut, enableShortcut, disableShortcut } = useShortcut();
const [enable, setEnable] = useState<boolean>(true);
const handleClick = useCallback(() => {
setEnable((prev) => {
if (prev) {
disableShortcut('Ctrl+a');
} else {
enableShortcut('Ctrl+a');
}
return !prev;
});
}, []);
// RegisterShortcut should be invoked in useEffect.
useEffect(() => {
registerShortcut('Ctrl+a', (event) => {
// invoked when key Control and key A pressed and enable is true.
console.log('You pressed Control and A');
});
return () => {
unregisterShortcut('Ctrl+a');
};
}, []);
return <button onClick={handleClick}>{enable ? 'disable' : 'enable'}</button>;
}
6. Custom event filter, default behavior is filter event triggered by input/textarea/select or contentEditable element.
import React, { useEffect, useCallback, useState } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';
function App() {
return (
<ReactShortcutProvider options={{ filter: (event) => event.target.tagName !== 'INPUT' }}>
<Main />
</ReactShortcutProvider>
);
}
function Main() {
const { registerShortcut, unregisterShortcut } = useShortcut();
useEffect(() => {
registerShortcut('Ctrl+a', (event) => {
// listener is not invoked when focus on input element
console.log('You pressed Control and A');
});
return () => {
unregisterShortcut('Ctrl+a');
};
}, []);
return (
<div>
<input />
</div>
);
}
Actions | Accelerator | Matched |
---|---|---|
press ControlLeft press AltLeft release AltLeft press A |
Control+a |
✅ |
press ControlLeft press AltLeft press A |
Control+a |
❌ |
press ControlRight press A |
Control+a |
✅ |
press ControlRight press B release B press A |
Control+a |
✅ |
press ControlLeft press A |
ControlOrCommand+a |
✅ |
press MetaLeft press A |
ControlOrCommand+a |
✅ |
press ControlLeft press 1 press 2 release 1 release 2 press A release A press B release B press C release C press D |
Control+a+b+c+d |
✅ |
press ControlLeft press A release A press 1 release 1 press B release B press C release C press D |
Control+a+b+c+d |
❌ |
press ControlLeft press A release A press A release A |
Control+a+a |
✅ |
type Accelerator = string;
type Dispose = () => void;
type KeyboardEventListener = (event: KeyboardEvent) => void;
interface ReactShortcutOptions {
// work mode, default to true.
strict?: boolean;
// print the debug message, default to false.
debug?: boolean;
// filter some event which does not want to handled.
// default behavior is filter event triggered by input/textarea/select or contentEditable element.
filter?: Filter;
// the element to listen keyboard event. fallback to window if this options is not set.
scope?: React.RefObject<HTMLElement>;
}
interface ReactShortcutProviderProps {
options?: ReactShortcutOptions;
children?: ReactNode;
}
interface ReactShortcutContextValue {
registerShortcut(accelerator: Accelerator, callback: KeyboardEventListener): boolean;
unregisterShortcut(accelerator: Accelerator): boolean;
enableShortcut(accelerator: Accelerator): boolean;
disableShortcut(accelerator: Accelerator): boolean;
isShortcutRegistered(accelerator: Accelerator): boolean;
getCurrentKeyPressed(): Accelerator;
onKeydown(listener: KeyboardEventListener): Dispose;
onKeyup(listener: KeyboardEventListener): Dispose;
}
Shortcut description, consist of multiple modifiers or normal keys join with +
,for example Ctrl+Alt+a
. All supported keys have list above. The order of modifiers does not affect, so the Ctrl+Alt+a
and Alt+Ctrl+a
are exact the same. But Ctrl+Alt+a+b
is not equal to Ctrl+Alt+b+a
. Modifiers must preceding normal keys, a+Ctrl
is invalid.
React Context Provider of react-use-shortcuts
. The most common used case is wrap in the root react component. You can also apply multiple ReactShortcutProvider
to different part of your page to achieve scoped shortcut register.
React Hook, used to get react-use-shortcuts
API.
ReactShortcutContextValue.registerShortcut: (accelerator: Accelerator, callback: KeyboardEventListener) => boolean;
Register shortcut handler, return false if current shortcut has registered or current shortcut is invalid.
Unregister shortcut handler, return false if current shortcut has not registered or shortcut is invalid.
enable shortcut, return false if current shortcut has not registered or shortcut is invalid.
disable shortcut, return false if current shortcut has not registered or shortcut is invalid.
Return true is current short has registered.
Return current keys pressed.
Register keydown
keyboardEvent listener on element attached, unlike registerShortcut
, listener will be invoked whenever key pressed.
Register keyup
keyboardEvent listener on element attached, unlike registerShortcut
, listener will be invoked whenever key released.If you pressed Command
key on MacOS, the keyup
event may be not triggered because it is a browser default behavior, more detail see: electron/electron#5188.
-
Chrome ≥ 48
-
Firefox ≥ 38
-
Safari ≥ 10.1
-
Edge ≥ 79
Features | react-use-shortcuts | react-hotkeys-hook | react-hot-keys |
---|---|---|---|
Dynamic register | ✅ | ❌ | ❌ |
Page scoped register | ✅ | ✅ | ❌ |
Strict/Loose mode | ✅ | ❌ | ❌ |
Dynamic enable/disable shortcut registered | ✅ | ✅ | ❌ |
Normal key combinations | ✅ | ✅ | ✅ |
Namespace | ❌ | ❌ | ✅ |
Shortcuts validation | ✅ | ❌ | ❌ |
Used React ≤ 16.8.0 | ❌ | ❌ | ✅ |
Distributed under the MIT License. See LICENSE
for more information.