Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useEffect, useRef, useCallback } from "react";
interface ShortcutOptions {
/** Requiere Ctrl (o Cmd en Mac). Defecto: false */
ctrl?: boolean;
/** Requiere Shift. Defecto: false */
shift?: boolean;
/** Requiere Alt (Opción en Mac). Defecto: false */
alt?: boolean;
/** Requiere Meta (Cmd en Mac, Win en Windows). Defecto: false */
meta?: boolean;
/** Llama a event.preventDefault(). Defecto: true */
preventDefault?: boolean;
/** Solo se dispara cuando esto es true. Defecto: true */
enabled?: boolean;
/** Elemento objetivo. Defecto: document */
target?: EventTarget | null;
}
function useKeyboardShortcut(
key: string,
callback: (event: KeyboardEvent) => void,
options: ShortcutOptions = {}
): void {
const {
ctrl = false,
shift = false,
alt = false,
meta = false,
preventDefault = true,
enabled = true,
target,
} = options;
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
useEffect(() => {
if (!enabled) return;
const eventTarget = target ?? document;
const handler = (e: Event) => {
const event = e as KeyboardEvent;
// Normaliza la comparación de teclas (sin distinción de mayúsculas)
if (event.key.toLowerCase() !== key.toLowerCase()) return;
// Verifica teclas modificadoras
// Usa metaKey O ctrlKey para Cmd/Ctrl multiplataforma
const ctrlMatch = ctrl
? event.ctrlKey || event.metaKey
: !event.ctrlKey && !event.metaKey;
// Si la opción ctrl está establecida, omite la verificación individual de meta
const metaMatch = ctrl ? true : meta ? event.metaKey : !event.metaKey;
const shiftMatch = shift ? event.shiftKey : !event.shiftKey;
const altMatch = alt ? event.altKey : !event.altKey;
if (!ctrlMatch || !shiftMatch || !altMatch || (!ctrl && !metaMatch)) {
return;
}
if (preventDefault) {
event.preventDefault();
}
callbackRef.current(event);
};
eventTarget.addEventListener("keydown", handler);
return () => eventTarget.removeEventListener("keydown", handler);
}, [key, ctrl, shift, alt, meta, preventDefault, enabled, target]);
}
/**
* useKeyboardShortcuts
* Registra múltiples atajos a la vez.
*/
function useKeyboardShortcuts(
shortcuts: Array<{
key: string;
callback: (event: KeyboardEvent) => void;
options?: ShortcutOptions;
}>
): void {
const shortcutsRef = useRef(shortcuts);
useEffect(() => {
shortcutsRef.current = shortcuts;
}, [shortcuts]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
for (const shortcut of shortcutsRef.current) {
const opts = shortcut.options ?? {};
const {
ctrl = false,
shift = false,
alt = false,
meta = false,
preventDefault = true,
enabled = true,
} = opts;
if (!enabled) continue;
if (e.key.toLowerCase() !== shortcut.key.toLowerCase()) continue;
const ctrlMatch = ctrl
? e.ctrlKey || e.metaKey
: !e.ctrlKey && !e.metaKey;
const metaMatch = ctrl ? true : meta ? e.metaKey : !e.metaKey;
const shiftMatch = shift ? e.shiftKey : !e.shiftKey;
const altMatch = alt ? e.altKey : !e.altKey;
if (!ctrlMatch || !shiftMatch || !altMatch || (!ctrl && !metaMatch)) {
continue;
}
if (preventDefault) e.preventDefault();
shortcut.callback(e);
break; // Solo dispara el primer atajo coincidente
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
}Cuándo usarlo: Deseas agregar atajos de teclado como Ctrl+K para una paleta de comandos, Ctrl+S para guardar, Escape para cerrar un modal, o teclas de flecha para navegación.
"use client";
import { useState } from "react";
function CommandPalette() {
const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState("");
// Ctrl+K o Cmd+K abre la paleta
useKeyboardShortcut("k", () => setIsOpen(true), { ctrl: true });
// Escape la cierra
useKeyboardShortcut("Escape", () => setIsOpen(false), {
enabled: isOpen,
preventDefault: false,
});
if (!isOpen) return null;
return (
<div
style={{
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.5)",
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
paddingTop: 100,
zIndex: 1000,
}}
>
<div
style={{
background: "#fff",
borderRadius: 12,
padding: 16,
width: 500,
maxWidth: "90vw",
boxShadow: "0 16px 48px rgba(0,0,0,0.2)",
}}
>
<input
autoFocus
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Escribe un comando..."
style={{
width: "100%",
padding: 12,
fontSize: 16,
border: "1px solid #e0e0e0",
borderRadius: 8,
outline: "none",
}}
/>
<div style={{ marginTop: 8, color: "#666", fontSize: 14 }}>
Presiona Escape para cerrar
</div>
</div>
</div>
);
}
function EditorWithShortcuts() {
const [content, setContent] = useState("Hola, mundo!");
const [saved, setSaved] = useState(false);
// Ctrl+S para guardar
useKeyboardShortcut("s", () => {
console.log("Guardando:", content);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
}, { ctrl: true });
// Ctrl+Shift+Z para rehacer
useKeyboardShortcut("z", () => {
console.log("Rehacer");
}, { ctrl: true, shift: true });
return (
<div>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
style={{ width: "100%", height: 200 }}
/>
{saved && <span style={{ color: "green" }}>¡Guardado!</span>}
</div>
);
}Lo que esto demuestra:
enabled)ctrl: true, el hook coincide con event.ctrlKey (Windows/Linux) y event.metaKey (Mac Cmd), por lo que Ctrl+K y Cmd+K funcionan.event.key se compara sin distinción de mayúsculas, por lo que "k" coincide con k y K (con Shift).true para evitar los valores predeterminados del navegador (p. ej., Ctrl+S abriendo el diálogo Guardar). Establece en false para teclas como Escape donde deseas que el comportamiento predeterminado continúe.false, el efecto omite el registro completamente, evitando listeners innecesarios.useKeyboardShortcuts registra un listener único para múltiples atajos, interrumpiendo después del primer coincidencia para eficiencia.| Parámetro | Tipo | Defecto | Descripción |
|---|---|---|---|
key | string | - | El valor event.key (p. ej., "k", "Escape", "ArrowDown") |
callback | (event: KeyboardEvent) => void | - | Manejador a llamar cuando se dispara el atajo |
options.ctrl | boolean | false | Requiere Ctrl (o Cmd en Mac) |
options.shift | boolean | false | Requiere Shift |
options.alt | boolean | false | Requiere Alt (Opción en Mac) |
options.meta | boolean | false | Requiere Meta (Cmd en Mac) |
options.preventDefault | boolean | true | Llama a event.preventDefault() |
options.enabled | boolean | true | Si el atajo está activo |
options.target | EventTarget | document | Objetivo de evento personalizado |
Secuencia de teclas (acorde): Detecta secuencias de múltiples teclas como g seguido de h para navegación estilo GitHub:
function useKeySequence(keys: string[], callback: () => void, timeout = 1000) {
const indexRef = useRef(0);
const timerRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key.toLowerCase() === keys[indexRef.current].toLowerCase()) {
indexRef.current++;
clearTimeout(timerRef.current);
if (indexRef.current === keys.length) {
callback();
indexRef.current = 0;
} else {
timerRef.current = setTimeout(() => {
indexRef.current = 0;
}, timeout);
}
} else {
indexRef.current = 0;
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [keys, callback, timeout]);
}Limitado a elemento: Pasa un ref como target para solo escuchar atajos cuando un elemento específico tiene enfoque:
const inputRef = useRef<HTMLInputElement>(null);
useKeyboardShortcut("Enter", handleSubmit, {
target: inputRef.current,
preventDefault: false,
});event.key son strings de la especificación KeyboardEvent.key (p. ej., "Escape", "ArrowUp", "a").KeyboardEvent completo para inspección avanzada (p. ej., event.repeat para teclas mantenidas).k en una entrada de texto dispara Ctrl+K si Ctrl se mantiene presionado. Solución: Verifica event.target y omite si es un elemento input, textarea o contenteditable.ctrl coincide con ctrlKey y metaKey por defecto para compatibilidad multiplataforma.event.key refleja el carácter ("a"), mientras que event.code refleja la tecla física ("KeyA"). Los diseños que no son QWERTY pueden diferir. Solución: Usa event.key para atajos de caracteres, event.code para atajos basados en posición.keydown repetidos. Solución: Verifica event.repeat y omite si solo deseas el primer presionamiento.| Paquete | Nombre del Hook | Notas |
|---|---|---|
react-hotkeys-hook | useHotkeys | Más popular, atajos basados en strings |
ahooks | useKeyPress | Detección simple de presión de tecla |
@uidotdev/usehooks | useKeyPress | Minimal, tecla única |
cmdk | Built-in | Componente de paleta de comandos completo |
kbar | Built-in | Paleta de comandos con manejo de atajos |
Cuando ctrl: true está establecido, el hook coincide con event.ctrlKey (Windows/Linux) y event.metaKey (Mac Cmd). Esto significa que Ctrl+K en Windows y Cmd+K en Mac disparan el mismo atajo.
La mayoría de los atajos de teclado anulan los valores predeterminados del navegador (p. ej., Ctrl+S dispara el diálogo Guardar). Establecer preventDefault: true detiene la acción predeterminada del navegador. Establécelo en false para teclas como Escape donde deseas que el comportamiento predeterminado continúe.
useKeyboardShortcut registra un listener keydown único para un atajo.useKeyboardShortcuts registra un listener keydown único que verifica múltiples atajos, interrumpiendo después del primer coincidencia. Es más eficiente para muchos atajos.Cuando enabled es false, el efecto omite el registro completamente. No hay un listener de eventos adjunto al documento. Esto es útil para atajos que solo deben estar activos en ciertos estados (p. ej., Escape solo cuando un modal está abierto).
Verifica event.target dentro del callback y omite si es un elemento input, textarea o contenteditable:
useKeyboardShortcut("k", (e) => {
const tag = (e.target as HTMLElement).tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return;
setIsOpen(true);
}, { ctrl: true });Verifica event.repeat dentro del callback:
useKeyboardShortcut("s", (e) => {
if (e.repeat) return;
save();
}, { ctrl: true });No. Los navegadores reservan ciertos atajos (cerrar pestaña, nueva pestaña) y no pueden ser interceptados por JavaScript. Elige atajos que los navegadores no reserven. Ctrl+K generalmente es seguro.
event.key refleja el carácter producido ("a", "k", "Escape").event.code refleja la posición de la tecla física ("KeyA", "KeyK").event.key para atajos basados en caracteres.El parámetro key es un string plano que coincide con valores de KeyboardEvent.key. La interfaz ShortcutOptions utiliza todas las propiedades booleanas opcionales (ctrl, shift, alt, meta) con valores predeterminados desestructurados en el cuerpo de la función.
Usa la variación useKeySequence de la sección Variaciones. Rastrea la posición actual en el array de teclas mediante un ref y se reinicia después de un timeout si la secuencia no se completa.
Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥