React library to manage your application's keyboard shortcuts.
npm i react-kbs
A shortcut is defined using an object with at least two fields: shortcut
and handler
.
shortcut
is a definition of the key combination that must be used to trigger the shortcut. It can be:- a string defining the keyboard's key (case-insensitive). Example:
'a'
. - an object of the form
{ key: string; ctrl?: boolean; shift?: boolean; alt?: boolean }
. Example:{ key: 's', ctrl: true }
. - an object of the form
{ code: string; ctrl?: boolean; shift?: boolean; alt?: boolean }
. Example:{ code: 'Digit1', shift: true }
. - an array of such strings and/or objects. This allows to define aliases for the same handler. Example:
['/', { key: 'k', ctrl: true }]
- a string defining the keyboard's key (case-insensitive). Example:
handler
is the function that will be called when the shortcut is triggered.meta
is an optional object that can be used to store any additional information about the shortcut. It will be available in the data returned byuseKbsGlobalList()
.maxFrequency
is an optional number that defines the maximum number of times the shortcut can be triggered per second. This only affects repeated triggers of the same shortcut when a key is held down.
Use key
when you want to refer to the character written by typing on the key
and code
when you want to use the physical key code (e.g. Digit1
for the 1
key).
There are some things to note about the behavior of shortcuts in react-kbs
:
- On macOS,
ctrl
automatically maps to thecommand
key. This means that if a shortcut is defined with{ key: 's', ctrl: true }
, theCtrl+S
combination must be pressed on Linux and Windows, whereasCmd+S
must be pressed on macOS. - By default, if the
shift
property is not specified, the state of the Shift key doesn't matter. In other words, the shortcut will be triggered with or without the Shift key being pressed. This is in order to make the shortcuts case-insensitive and to support any keyboard layout (the need to press Shift for some keys depend on the user's layout).
Global shortcuts are shortcuts that are active anywhere on the page, as long as no specific element has the focus.
To setup global shortcuts, you need to render the KbsProvider
component high
in your React component tree:
import { KbsProvider } from 'react-kbs';
export default function App() {
return (
<KbsProvider>
<SubComponent />
</KbsProvider>
);
}
Then, anywhere down the tree, you can call the useKbsGlobal
hook with an array
of shortcut definitions to add global shortcuts to the context:
import { useKbsGlobal } from 'react-kbs';
export function MyComponent() {
const [counter, setCounter] = useState(0);
useKbsGlobal([
{
shortcut: 'i',
handler() {
setCounter((current) => current + 1);
},
},
{
shortcut: 'd',
handler() {
setCounter((current) => current - 1);
},
},
]);
return <div>Count: {counter}</div>;
}
Local shortcuts are shortcuts that are only active when a specific element on the page, or one of its children, is focused.
It is possible to nest such elements. In that case, the first shortcut definition that matches the user input will apply.
To setup local shortcuts, call the useKbs
hook with an array of
shortcut definitions and pass the value it returns
as props to the element for which you want to enable the shortcuts. The following
attributes will be set:
tabIndex={0}
: makes the element focusableonKeyDown={handler}
: event handler
function MyComponent(props) {
const shortcutProps = useKbs([
{
shortcut: ['delete', 'backspace'],
handler: props.onDelete,
},
]);
return (
<div {...shortcutProps}>
<SomeContent />
</div>
);
}
When no particular element is focused, global shortcuts take effect.
When simple focusable elements (buttons, links, elements that have a tabindex
),
are focused, global or local shortcuts will usually be triggered, unless one of
the following conditions applies:
- The element is an HTML
<input>
,<textarea>
, or<select>
. - The element is
contenteditable
. - The element has the
data-kbs-ignore
attribute.
It may be useful in some cases to disable all global shortcuts (for example
when a dialog is open). To do this, call the useKbsDisableGlobal
hook:
import { useKbsDisableGlobal } from 'react-kbs';
function MyDialog({ open }) {
useKbsDisableGlobal(open);
}
To get the list of all currently defined global shortcuts, call the
useKbsGlobalList
hook:
import { useKbsGlobalList } from 'react-kbs';
function MyComponent() {
const shortcuts = useKbsGlobalList();
return <ShortcutHelp shortcuts={shortcuts} />;
}
It is possible to pass optional metadata with each shortcut in the meta
field.
The metadata will then be available in the objects returned by useKbsGlobalList()
.
react-kbs
doesn't use the metadata object, which can have any shape. A common
use case is to dynamically render a list with all shortcuts and documentation
about them.
useKbsGlobal([
{
shortcut: 'delete',
handler: handleDelete,
meta: {
description: 'Delete the selected element',
},
},
]);
If using TypeScript, you will have to define the shape of the meta
object
to suit your needs:
import 'react-kbs';
declare module 'react-kbs' {
interface KbsMetadata {
description: string;
}
}