react-use-shortcuts v1.0.0
react-use-shortcuts
Full shortcut solution for react app.
Features
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.
Installation
# npm
npm install react-use-shortcuts
# yarn
yarn add react-use-shortcuts
# pnpm
pnpm add react-use-shortcuts
Supported Keys
Modfiers
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. |
Normal Keys
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 |
Example
1. Register single key shortcut.
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>;
}
2. Register shortcut combined with modifier and normal key.
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>;
}
3. Register scoped shortcut.
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>;
}
‼️ Important: Set element tabIndex property to -1 is to make this element to focusable. Scoped shortcut will not work without this.
4. Loose mode.
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>;
}
5. Normal keys combinations.
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>;
}
6. Dynamic enable/disable shortcut.
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>
);
}
Some shortcut match rules example.
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 | ✅ |
API Reference
Interface Definition
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;
}
Accelerator: string;
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.
ReactShortcutProvider: React.FC<ReactShortcutProviderProps>;
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.
useShortcut: () => ReactShortcutContextValue;
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.
ReactShortcutContextValue.unregisterShortcut: (accelerator: Accelerator) => boolean;
Unregister shortcut handler, return false if current shortcut has not registered or shortcut is invalid.
ReactShortcutContextValue.enableShortcut: (accelerator: Accelerator) => boolean;
enable shortcut, return false if current shortcut has not registered or shortcut is invalid.
ReactShortcutContextValue.disableShortcut: (accelerator: Accelerator) => boolean;
disable shortcut, return false if current shortcut has not registered or shortcut is invalid.
ReactShortcutContextValue.isShortcutRegistered: (accelerator: Accelerator) => boolean;
Return true is current short has registered.
ReactShortcutContextValue.getCurrentKeyPressed: () => Accelerator;
Return current keys pressed.
ReactShortcutContextValue.onKeydown: (listener: KeyboardEventListener) => Dispose;
Register keydown
keyboardEvent listener on element attached, unlike registerShortcut
, listener will be invoked whenever key pressed.
ReactShortcutContextValue.onKeyup: (listener: KeyboardEventListener) => Dispose;
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: https://github.com/electron/electron/issues/5188.
Browser Compatibility
Chrome ≥ 48
Firefox ≥ 38
Safari ≥ 10.1
Edge ≥ 79
Alternatives
Comparisons
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 | ❌ | ❌ | ✅ |
License
Distributed under the MIT License. See LICENSE
for more information.
2 years ago