useHotkeys

import { useHotkeys } from "reshaped";

useHotkeys triggers callbacks passed to it when a hotkey is pressed. It accepts an object with hotkeys defined as object keys and callbacks as object values.

You can pass a single hotkey or multiple hotkeys separated by commas, which will trigger a callback when any of them is pressed. Key codes passed to the hook should match the event.key values.

// Single hotkey
useHotkeys({ n: createNewTask });

// An array of hotkeys for the same callback
useHotkey({ "n, t": createNewTask });

useHotkeys can be used for combinations of keys pressed together by separating them with a + sign. Spacing and letter casing do not matter, as useHotkeys automatically formats all the values passed to it.

useHotkeys({ "Shift + b": switchView });

For cross-platform hotkeys, you can use the special mod key, which will trigger for both cmd and ctrl keyboard keys.

useHotkeys({ "mod + b": switchView });

If you want to prevent the default behavior of the pressed keys, you can use the event argument provided by the callback. This also means you can prevent the default behavior only when a certain condition is met, not every time the hotkey is pressed.

useHotkeys({
  n: (event) => {
    event.preventDefault();
  },
});

By default, useHotkeys uses a window event handler, which means the callback gets triggered anytime the user presses the hotkey. If you want your action to be triggered only for a specific element or area of the page, you can use the ref returned from the hook.

Note that a single ref is used for all hotkeys passed to it, which means you can assign all of them to the same element. If you want to assign them to different elements, you can use multiple useHotkeys calls.

function Example() {
  const [count, setCount] = React.useState(0);
  const value = count > 0 ? `Value ${count}` : "";
  const { ref } = useHotkeys({
    ArrowUp: () => setCount((prev) => prev + 1),
    ArrowDown: () => setCount((prev) => Math.max(0, prev - 1)),
  });

  return (
    <TextField
      inputAttributes={{ ref }}
      placeholder="Use up and down arrow keys"
      value={value}
    />
  );
}

When using with TypeScript, it is likely that you will need to provide a more specific type for the DOM element you're using it with. As with other React hooks, you can pass that element type with a generic:

const { ref } = useHotkeys<HTMLInputElement>({ "Shift + Enter": submitForm });

If you already have a ref for the element and don't want useHotkeys to create a new one, you can pass your own ref to the useHotkeys options.

const { ref } = useHotkeys(
  {
    ArrowUp: () => setCount((prev) => prev + 1),
  },
  [], // dependences array, mentioned below
  { ref: myRef },
);

Additionally, useHotkeys returns a checkHotkeyState function which you can use to check if any hotkey is currently pressed. You can then use its returned boolean value to conditionally render content based on the state. It's possible to use this function result without passing a callback for the hotkey.

const { checkHotkeyState } = useHotkeys({ "Shift + b": null });

// User pressed Shift + b

checkHotkeyState("Shift + b"); // true
checkHotkeyState("c"); // false

Since all callbacks are bound with useEffect, they might also get cached for future calls. Similar to effects, useHotkeys supports a dependency array:

const { count } = props;

useHotkeys(
  {
    b: () => console.log(count),
  },
  [count],
);

If you want to attach the keyboard events to a ref and you already have a ref created in your component, you can pass it as an option:

const { ref } = useHotkeys({...}, [], { ref: inputRef });

You can temporarily turn off all hotkeys with the disabled option. This can be useful when your component has an inactive or disabled state and you don't want to handle it separately in each hotkey handler.

useHotkeys({...}, [], { disabled: !isActive });

Some keys are reserved in browsers by default. For example, pressing the up and down arrow keys scrolls the webpage. If you want to prevent that default behavior for all hotkeys defined in the hook, use the preventDefault option.

useHotkeys({...}, [], { preventDefault: true });
(
  hotkeys: Record<string, (event) => void | null>,
  deps: unknown[],
  options: { ref: React.Ref<HTMLElement>, disabled?: boolean, preventDefault?: boolean }
) => {
  ref: React.RefObject<HTMLElement>,
	checkHotkeyState: (hotkey: string) => boolean
}