# Keyboard shortcuts

Add keyboard shortcuts for common call actions like mute/unmute.

## Best Practices

- Use `hotkeys-js` library for cleaner shortcut registration.
- Detect macOS with `navigator.userAgent` to use `⌘` instead of `Ctrl`.
- Place shortcut hook in same component as CallControls for logical grouping.
- Always clean up event listeners in useEffect return function.
- Common shortcuts: `Ctrl/⌘ + D` (toggle audio), `Ctrl/⌘ + E` (toggle video).

## Define custom `useKeyboardShortcuts` hook

As this hook will introduce keyboard shortcuts related to [`CallControls`](https://getstream.io/video/docs/react/v2/ui-components/call/call-controls/) (mute/unmute audio) we should ideally invoke it in the same component to group logical parts together for easier debugging later. The same goes for your other keyboard shortcuts you might introduce to your application. Learn how to adjust your `CallControls` component in [the Replacing Call Controls guide](https://getstream.io/video/docs/react/v2/ui-cookbook/replacing-call-controls/).

```ts
import { useEffect } from "react";
import { useCallStateHooks } from "@stream-io/video-react-sdk";

const isMacOS = () => !!navigator.userAgent.match(/(Mac\s?OS)/g)?.length;

export const useKeyboardShortcuts = () => {
  const { useCameraState, useMicrophoneState } = useCallStateHooks();
  const { microphone } = useMicrophoneState();
  const { camera } = useCameraState();

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (
        e.key !== "d" ||
        (!e.metaKey && isMacOS()) ||
        (!e.ctrlKey && !isMacOS())
      )
        return;
      e.preventDefault();

      microphone.toggle().catch(console.error);
    };

    window.addEventListener("keydown", handleKeyDown);

    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [microphone]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (
        e.key !== "e" ||
        (!e.metaKey && isMacOS()) ||
        (!e.ctrlKey && !isMacOS())
      )
        return;
      e.preventDefault();

      camera.toggle().catch(console.error);
    };

    window.addEventListener("keydown", handleKeyDown);

    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [camera]);
};
```

### Refactor with the use of `hotkeys-js`

The previous implementation looks a bit messy and hard to read so we'll refactor this hook with the help of the [`hotkeys-js`](https://github.com/jaywcjlove/hotkeys-js) which makes shortcut registering and handling much easier.

```ts
import { useEffect } from "react";
import hotkeys from "hotkeys-js";
import { useCallStateHooks } from "@stream-io/video-react-sdk";

const isMacOS = () => !!navigator.userAgent.match(/(Mac\s?OS)/g)?.length;

export const useKeyboardShortcuts = () => {
  const { useCameraState, useMicrophoneState } = useCallStateHooks();
  const { microphone } = useMicrophoneState();
  const { camera } = useCameraState();

  useEffect(() => {
    const shortcuts = "cmd+d,ctrl+d";
    hotkeys(shortcuts, (e, ke) => {
      if (isMacOS() && ke.shortcut !== "cmd+d") return;
      e.preventDefault();

      microphone.toggle().catch(console.error);
    });

    return () => hotkeys.unbind(shortcuts);
  }, [microphone]);

  useEffect(() => {
    const shortcuts = "cmd+e,ctrl+e";
    hotkeys(shortcuts, (e, ke) => {
      if (isMacOS() && ke.shortcut !== "cmd+e") return;
      e.preventDefault();

      camera.toggle().catch(console.error);
    });

    return () => hotkeys.unbind(shortcuts);
  }, [camera]);
};
```

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react/v2/advanced/keyboard-shortcuts/](https://getstream.io/video/docs/react/v2/advanced/keyboard-shortcuts/).