useKeyCombo

UI/Interaction

A React hook that detects keyboard combinations, perfect for implementing keyboard shortcuts and complex interactions.

Demo

Try these keyboard combinations:

Ctrl + S
Ctrl + Z
Ctrl + Shift + Z
Ctrl + A

Note: Make sure this window has focus for the key combinations to work.

Installation

npm install @thibault.sh/hooks

Usage

import { useKeyCombo } from '@thibault.sh/hooks/useKeyCombo';

function Editor() {
  const isSaveCombo = useKeyCombo(['Control', 's']);
  const isUndoCombo = useKeyCombo(['Control', 'z']);
  const isRedoCombo = useKeyCombo(['Control', 'Shift', 'z']);

  React.useEffect(() => {
    if (isSaveCombo) {
      handleSave();
    } else if (isUndoCombo) {
      handleUndo();
    } else if (isRedoCombo) {
      handleRedo();
    }
  }, [isSaveCombo, isUndoCombo, isRedoCombo]);

  return (
    <div>
      <p>Available shortcuts:</p>
      <ul>
        <li>Ctrl + S: Save</li>
        <li>Ctrl + Z: Undo</li>
        <li>Ctrl + Shift + Z: Redo</li>
      </ul>
      {/* Editor content */}
    </div>
  );
}

API

Parameters

  • targetCombo
    string[]

    Array of keys that make up the combination (e.g., ["Control", "Shift", "a"])

Returns

{boolean} Boolean indicating if the combination is currently active

Key Names:

  • • Modifier keys: "Control", "Shift", "Alt", "Meta"
  • • Single characters: "a"-"z", "0"-"9"
  • • Special keys: "Enter", "Escape", "Delete", "Tab"
  • • Function keys: "F1"-"F12"

Features

  • Order Independent

    Key order doesn't matter in the combination

  • Multiple Modifiers

    Support for combinations with multiple modifier keys

  • Type Safety

    Full TypeScript support for key names

  • Cleanup

    Automatically removes event listeners on unmount

Rich Text Editor Example

import { useKeyCombo } from '@thibault.sh/hooks';
import { useState } from 'react';

interface TextStyle {
  isBold: boolean;
  isItalic: boolean;
  isUnderline: boolean;
}

function RichTextEditor() {
  const [style, setStyle] = useState<TextStyle>({
    isBold: false,
    isItalic: false,
    isUnderline: false
  });

  const isBoldCombo = useKeyCombo(['Control', 'b']);
  const isItalicCombo = useKeyCombo(['Control', 'i']);
  const isUnderlineCombo = useKeyCombo(['Control', 'u']);

  React.useEffect(() => {
    if (isBoldCombo) {
      setStyle(prev => ({ ...prev, isBold: !prev.isBold }));
    }
    if (isItalicCombo) {
      setStyle(prev => ({ ...prev, isItalic: !prev.isItalic }));
    }
    if (isUnderlineCombo) {
      setStyle(prev => ({ ...prev, isUnderline: !prev.isUnderline }));
    }
  }, [isBoldCombo, isItalicCombo, isUnderlineCombo]);

  return (
    <div>
      <div className="toolbar">
        <button className={style.isBold ? 'active' : ''}>
          Bold (Ctrl+B)
        </button>
        <button className={style.isItalic ? 'active' : ''}>
          Italic (Ctrl+I)
        </button>
        <button className={style.isUnderline ? 'active' : ''}>
          Underline (Ctrl+U)
        </button>
      </div>
      <textarea
        style={{
          fontWeight: style.isBold ? 'bold' : 'normal',
          fontStyle: style.isItalic ? 'italic' : 'normal',
          textDecoration: style.isUnderline ? 'underline' : 'none'
        }}
      />
    </div>
  );
}

Application Shortcuts Example

import { useKeyCombo } from '@thibault.sh/hooks';
import { useEffect } from 'react';

function AppShortcuts() {
  const isNewFileCombo = useKeyCombo(['Control', 'n']);
  const isOpenFileCombo = useKeyCombo(['Control', 'o']);
  const isSaveCombo = useKeyCombo(['Control', 's']);
  const isSaveAsCombo = useKeyCombo(['Control', 'Shift', 's']);
  const isPrintCombo = useKeyCombo(['Control', 'p']);
  const isCloseTabCombo = useKeyCombo(['Control', 'w']);
  const isQuitCombo = useKeyCombo(['Control', 'q']);

  useEffect(() => {
    const handleShortcuts = () => {
      if (isNewFileCombo) {
        handleNewFile();
      } else if (isOpenFileCombo) {
        handleOpenFile();
      } else if (isSaveAsCombo) {
        // Handle Save As before Save to prevent conflict
        handleSaveAs();
      } else if (isSaveCombo) {
        handleSave();
      } else if (isPrintCombo) {
        handlePrint();
      } else if (isCloseTabCombo) {
        handleCloseTab();
      } else if (isQuitCombo) {
        handleQuit();
      }
    };

    handleShortcuts();
  }, [
    isNewFileCombo,
    isOpenFileCombo,
    isSaveCombo,
    isSaveAsCombo,
    isPrintCombo,
    isCloseTabCombo,
    isQuitCombo
  ]);

  return (
    <div className="help-panel">
      <h2>Keyboard Shortcuts</h2>
      <table>
        <tbody>
          <tr>
            <td>New File</td>
            <td>Ctrl + N</td>
          </tr>
          <tr>
            <td>Open File</td>
            <td>Ctrl + O</td>
          </tr>
          <tr>
            <td>Save</td>
            <td>Ctrl + S</td>
          </tr>
          <tr>
            <td>Save As</td>
            <td>Ctrl + Shift + S</td>
          </tr>
          <tr>
            <td>Print</td>
            <td>Ctrl + P</td>
          </tr>
          <tr>
            <td>Close Tab</td>
            <td>Ctrl + W</td>
          </tr>
          <tr>
            <td>Quit</td>
            <td>Ctrl + Q</td>
          </tr>
        </tbody>
      </table>
    </div>
  );
}