Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useEffect, useRef, useCallback } from "react";
interface ShortcutOptions {
/** Exige Ctrl (ou Cmd no Mac). Padrão: false */
ctrl?: boolean;
/** Exige Shift. Padrão: false */
shift?: boolean;
/** Exige Alt (Option no Mac). Padrão: false */
alt?: boolean;
/** Exige Meta (Cmd no Mac, Win no Windows). Padrão: false */
meta?: boolean;
/** Chama event.preventDefault(). Padrão: true */
preventDefault?: boolean;
/** Dispara apenas quando este valor for true. Padrão: true */
enabled?: boolean;
/** Elemento alvo. Padrão: 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 a comparação da tecla (case-insensitive)
if (event.key.toLowerCase() !== key.toLowerCase()) return;
// Verifica teclas modificadoras
// Usa metaKey OU ctrlKey para Cmd/Ctrl multiplataforma
const ctrlMatch = ctrl
? event.ctrlKey || event.metaKey
: !event.ctrlKey && !event.metaKey;
// Se a opção ctrl estiver definida, ignora a verificação 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últiplos atalhos de uma 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; // Dispara apenas o primeiro atalho correspondente
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
}Quando usar isso: Você quer adicionar atalhos de teclado como Ctrl+K para uma paleta de comandos, Ctrl+S para salvar, Escape para fechar um modal, ou teclas de seta para navegação.
"use client";
import { useState } from "react";
function CommandPalette() {
const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState("");
// Ctrl+K ou Cmd+K abre a paleta
useKeyboardShortcut("k", () => setIsOpen(true), { ctrl: true });
// Escape fecha a paleta
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="Digite um comando..."
style={{
width: "100%",
padding: 12,
fontSize: 16,
border: "1px solid #e0e0e0",
borderRadius: 8,
outline: "none",
}}
/>
<div style={{ marginTop: 8, color: "#666", fontSize: 14 }}>
Pressione Escape para fechar
</div>
</div>
</div>
);
}
function EditorWithShortcuts() {
const [content, setContent] = useState("Olá, mundo!");
const [saved, setSaved] = useState(false);
// Ctrl+S para salvar
useKeyboardShortcut("s", () => {
console.log("Salvando:", content);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
}, { ctrl: true });
// Ctrl+Shift+Z para refazer
useKeyboardShortcut("z", () => {
console.log("Refazer");
}, { 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" }}>Salvo!</span>}
</div>
);
}O que isso demonstra:
enabled)ctrl: true, o hook corresponde tanto a event.ctrlKey (Windows/Linux) quanto a event.metaKey (Mac Cmd), então Ctrl+K e Cmd+K funcionam.event.key é comparado sem distinção entre maiúsculas e minúsculas, então "k" corresponde a k e K (com Shift).true para impedir ações padrão do navegador (por exemplo, Ctrl+S abrindo a caixa de diálogo Salvar). Defina como false para teclas como Escape, onde você deseja que o comportamento padrão prossiga.enabled: Quando false, o efeito pula o registro inteiramente, evitando listeners desnecessários.useKeyboardShortcuts registra um único listener para múltiplos atalhos, parando após a primeira correspondência para eficiência.| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
key | string | - | O valor de event.key (por exemplo, "k", "Escape", "ArrowDown") |
callback | (event: KeyboardEvent) => void | - | Handler a ser chamado quando o atalho disparar |
options.ctrl | boolean | false | Exige Ctrl (ou Cmd no Mac) |
options.shift | boolean | false | Exige Shift |
options.alt | boolean | false | Exige Alt (Option no Mac) |
options.meta | boolean | false | Exige Meta (Cmd no Mac) |
options.preventDefault | boolean | true | Chama event.preventDefault() |
options.enabled | boolean | true | Se o atalho está ativo |
options.target | EventTarget | document | Alvo de evento personalizado |
Sequência de teclas (chord): Detecta sequências de múltiplas teclas como g seguida de h para navegação no 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]);
}Escopo para elemento: Passe um ref como target para escutar atalhos apenas quando um elemento específico tiver foco:
const inputRef = useRef<HTMLInputElement>(null);
useKeyboardShortcut("Enter", handleSubmit, {
target: inputRef.current,
preventDefault: false,
});event.key são strings da especificação KeyboardEvent.key (por exemplo, "Escape", "ArrowUp", "a").KeyboardEvent completo para inspeção avançada (por exemplo, event.repeat para teclas mantidas pressionadas).k em um campo de texto dispara Ctrl+K se Ctrl estiver pressionado. Correção: Verifique event.target e pule se for um elemento de entrada, textarea ou contenteditable.ctrl corresponde a ctrlKey e metaKey por padrão para compatibilidade multiplataforma.event.key reflete o caractere ("a"), enquanto event.code reflete a tecla física ("KeyA"). Layouts não QWERTY podem diferir. Correção: Use event.key para atalhos de caractere, event.code para atalhos baseados em posição.keydown repetidos. Correção: Verifique event.repeat e pule se você quiser apenas o primeiro pressionamento.| Pacote | Nome do Hook | Notas |
|---|---|---|
react-hotkeys-hook | useHotkeys | Mais popular, atalhos baseados em string |
ahooks | useKeyPress | Detecção simples de tecla pressionada |
@uidotdev/usehooks | useKeyPress | Mínimo, tecla única |
cmdk | Embutido | Componente completo de paleta de comandos |
kbar | Embutido | Paleta de comandos com manipulação de atalhos |
Quando ctrl: true é definido, o hook corresponde tanto a event.ctrlKey (Windows/Linux) quanto a event.metaKey (Mac Cmd). Isso significa que Ctrl+K no Windows e Cmd+K no Mac acionam o mesmo atalho.
A maioria dos atalhos de teclado substitui os padrões do navegador (por exemplo, Ctrl+S aciona a caixa de diálogo Salvar). Definir preventDefault: true impede a ação padrão do navegador. Defina como false para teclas como Escape, onde você deseja que o comportamento padrão prossiga.
useKeyboardShortcut registra um único listener keydown para um atalho.useKeyboardShortcuts registra um único listener keydown que verifica múltiplos atalhos, parando após a primeira correspondência. É mais eficiente para muitos atalhos.Quando enabled é false, o efeito pula o registro inteiramente. Nenhum listener de evento é anexado ao documento. Isso é útil para atalhos que devem estar ativos apenas em determinados estados (por exemplo, Escape apenas quando um modal está aberto).
Verifique event.target dentro do callback e pule se for um elemento de entrada, textarea ou contenteditable:
useKeyboardShortcut("k", (e) => {
const tag = (e.target as HTMLElement).tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return;
setIsOpen(true);
}, { ctrl: true });Verifique event.repeat dentro do callback:
useKeyboardShortcut("s", (e) => {
if (e.repeat) return;
save();
}, { ctrl: true });Não. Navegadores reservam certos atalhos (fechar aba, nova aba) e eles não podem ser interceptados por JavaScript. Escolha atalhos que os navegadores não reservem. Ctrl+K é geralmente seguro.
event.key reflete o caractere produzido ("a", "k", "Escape").event.code reflete a posição física da tecla ("KeyA", "KeyK").event.key para atalhos baseados em caracteres.O parâmetro key é uma string simples que corresponde aos valores de KeyboardEvent.key. A interface ShortcutOptions usa todas as propriedades booleanas opcionais (ctrl, shift, alt, meta) com padrões desestruturados no corpo da função.
Use a variação useKeySequence da seção Variações. Ela rastreia a posição atual no array de teclas via um ref e reseta após um timeout se a sequência não for concluída.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥