useComboKeyPress
A React hook that detects complex keyboard shortcuts with modifier keys (Ctrl, Shift, Alt, Meta).
Installation
npx shadcn@latest add https://r.fiberui.com/r/hooks/use-combo-key-press.jsonFeatures
- Complex Combos - Detects combinations like
Ctrl+Shift+S. - Modifier Support - First-class support for
ctrl,shift,alt, andmeta(Cmd). - Callback Mode - Executes a function when the combo is pressed.
- State Mode - Returns a boolean indicating if the combo is currently active.
- Cross-Platform - Handles
ctrlvsmetakey differences gracefully.
Source Code
View the full hook implementation in the Hook Source Code section below.
Interactive Examples
Basic Shortcut (Ctrl + S)
A common pattern for triggering actions like saving.
Save Shortcut (Ctrl/Cmd + S)
Press Ctrl + S to trigger save.
"use client";
import { useComboKeyPress } from "@repo/hooks/dom/use-combo-key-press";
import { Save } from "lucide-react";
import { useState } from "react";
export const Example1 = () => {
const [saved, setSaved] = useState(false);
// Detect Ctrl+S (Cmd+S)
useComboKeyPress({ key: "s", ctrl: true }, () => {
// e.preventDefault() is handled by default options
setSaved(true);
setTimeout(() => setSaved(false), 2000);
});
return (
<div className="flex w-full max-w-md flex-col items-center gap-6">
<h3 className="font-semibold">Save Shortcut (Ctrl/Cmd + S)</h3>
<div
className={`rounded-full p-4 transition-all duration-300 ${
saved
? "scale-110 bg-green-100 text-green-600"
: "bg-muted text-muted-foreground"
}`}
>
<Save className="h-8 w-8" />
</div>
<p className="text-center text-sm">
Press{" "}
<kbd className="bg-muted rounded border px-1 py-0.5 text-xs">
Ctrl
</kbd>{" "}
+{" "}
<kbd className="bg-muted rounded border px-1 py-0.5 text-xs">
S
</kbd>{" "}
to trigger save.
</p>
{saved && (
<div className="animate-in fade-in slide-in-from-bottom-2 text-sm font-medium text-green-600">
Saved successfully!
</div>
)}
</div>
);
};
useComboKeyPress({ key: "s", ctrl: true }, () => {
saveDocument();
});Select All (Cmd + A)
A common pattern for selecting all items in a list or text area.
Select All Shortcut
- Item 1
- Item 2
- Item 3
- Item 4
Press Ctrl/Cmd + A to select all items.
"use client";
import { useComboKeyPress } from "@repo/hooks/dom/use-combo-key-press";
import { Check } from "lucide-react";
import { useState } from "react";
export const Example2 = () => {
const [items, setItems] = useState([
{ id: 1, label: "Item 1", selected: false },
{ id: 2, label: "Item 2", selected: false },
{ id: 3, label: "Item 3", selected: false },
{ id: 4, label: "Item 4", selected: false },
]);
// Detect Ctrl+A (or Cmd+A on Mac)
useComboKeyPress({ key: "a", ctrl: true }, (e) => {
e.preventDefault(); // Prevent native text selection
setItems((prev) => prev.map((item) => ({ ...item, selected: true })));
});
const toggleSelection = (id: number) => {
setItems((prev) =>
prev.map((item) =>
item.id === id ? { ...item, selected: !item.selected } : item,
),
);
};
return (
<div className="flex w-full max-w-md flex-col gap-4">
<h3 className="font-semibold">Select All Shortcut</h3>
<div className="bg-muted/50 rounded-lg p-2">
<ul className="space-y-1">
{items.map((item) => (
<li
key={item.id}
onClick={() => toggleSelection(item.id)}
className={`flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm transition-colors ${
item.selected
? "bg-primary text-primary-foreground"
: "hover:bg-muted-foreground/10"
}`}
>
<span>{item.label}</span>
{item.selected && <Check className="h-4 w-4" />}
</li>
))}
</ul>
</div>
<p className="text-muted-foreground text-center text-xs">
Press <kbd className="font-mono">Ctrl/Cmd + A</kbd> to select
all items.
</p>
</div>
);
};
useComboKeyPress({ key: "a", ctrl: true }, (e) => {
e.preventDefault();
selectAllItems();
});API Reference
Signature
const isPressed = useComboKeyPress(combo, callback?, options?);Parameters
| Parameter | Type | Description |
|---|---|---|
combo | KeyCombo | Object defining the key combination. |
callback | (event) => void | Optional function to call when combo is matched. |
options | EventListenerOptions | Standard options. Defaults to { preventDefault: true }. |
KeyCombo Interface
| Property | Type | Description |
|---|---|---|
key | string | The main key to listen for (e.g., "s", "Enter"). |
ctrl | boolean | Require Control (or Command on Mac) key. |
shift | boolean | Require Shift key. |
alt | boolean | Require Alt (or Option) key. |
meta | boolean | Require Meta (Command/Windows) key specifically. |
Returns
| Type | Description |
|---|---|
boolean | true if the full combination is currently pressed. |
Hook Source Code
import { useState, useCallback, useRef, useEffect } from "react";
import {
useEventListener,
EventListenerOptions,
} from "@repo/hooks/dom/use-event-listener";
interface KeyCombo {
/** The main key (e.g., "a", "s", "Escape", "Enter") */
key: string;
/** Require Ctrl key (Cmd on Mac for cross-platform shortcuts) */
ctrl?: boolean;
/** Require Meta key (Cmd on Mac, Win on Windows) */
meta?: boolean;
/** Require Shift key */
shift?: boolean;
/** Require Alt key (Option on Mac) */
alt?: boolean;
}
/**
* useComboKeyPress - Detect keyboard shortcuts with modifier keys
*
* @param combo - The key combination to detect
* @param callback - Optional callback when combo is pressed
* @param options - Optional event listener options (preventDefault defaults to true)
* @returns boolean indicating if the combo is currently pressed
*
* @example
* // Using the boolean state
* const isSavePressed = useComboKeyPress({ key: "s", ctrl: true });
*
* // With callback (Ctrl+S or Cmd+S on Mac)
* useComboKeyPress({ key: "s", ctrl: true }, () => save());
*
* // Ctrl+Shift+A
* const isSelectAllPressed = useComboKeyPress(
* { key: "a", ctrl: true, shift: true },
* () => selectAll()
* );
*/
export function useComboKeyPress(
combo: KeyCombo,
callback?: (event: KeyboardEvent) => void,
options: EventListenerOptions = { preventDefault: true },
): boolean {
const [isPressed, setIsPressed] = useState(false);
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
const matchesCombo = useCallback(
(event: KeyboardEvent): boolean => {
const keyMatches =
event.key.localeCompare(combo.key, undefined, {
sensitivity: "base",
}) === 0;
const ctrlMatches = combo.ctrl
? event.ctrlKey || event.metaKey
: !event.ctrlKey && !event.metaKey;
const shiftMatches = combo.shift ? event.shiftKey : !event.shiftKey;
const altMatches = combo.alt ? event.altKey : !event.altKey;
return keyMatches && ctrlMatches && shiftMatches && altMatches;
},
[combo],
);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (matchesCombo(event)) {
setIsPressed(true);
callbackRef.current?.(event);
}
},
[matchesCombo],
);
const handleKeyUp = useCallback(
(event: KeyboardEvent) => {
if (matchesCombo(event)) {
setIsPressed(false);
}
},
[matchesCombo],
);
useEventListener("keydown", handleKeyDown, null, options);
useEventListener("keyup", handleKeyUp);
return isPressed;
}
useClickOutside
A React hook that detects clicks outside of a specified element and triggers a callback. Perfect for closing modals, dropdowns, and popovers when interacting with other parts of the UI.
useEventListener
A type-safe React hook for attaching event listeners to window, document, or any HTML element. Handles cleanup automatically, supports all standard options, and is SSR-safe.