Eventos de Teclado
Responda a pressionamentos de tecla, liberações e atalhos de teclado em componentes React.
Busque em todas as páginas da documentação
Responda a pressionamentos de tecla, liberações e atalhos de teclado em componentes React.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
| Propriedade React | Tipo TypeScript | Dispara Quando | Notas |
|---|---|---|---|
onKeyDown | React.KeyboardEvent<T> | Uma tecla é pressionada | Evento de teclado principal -- use este |
onKeyUp | React.KeyboardEvent<T> | Uma tecla é liberada | Dispara após a ação da tecla ser concluída |
onKeyPress | React.KeyboardEvent<T> | Uma tecla de caractere é pressionada | Depreciado -- não use em código novo |
Cartão de receita de referência rápida -- pronto para copiar e colar.
// Tecla Enter para enviar
function EnterSubmit() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
console.log("Enviado:", e.currentTarget.value);
}
};
return <input onKeyDown={handleKeyDown} placeholder="Pressione Enter" />;
}
// Tecla Escape para limpar / fechar
function EscapeToClear() {
const [value, setValue] = React.useState("");
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
setValue("");
e.currentTarget.blur();
}
};
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Escape para limpar"
/>
);
}
// Atalho de teclado com modificador (Cmd/Ctrl + S)
function SaveShortcut() {
React.useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
e.preventDefault();
console.log("Salvar acionado");
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
return <div>Pressione Cmd+S (Mac) ou Ctrl+S (Windows) para salvar</div>;
}Quando usar isso: Você precisa responder à entrada do teclado -- enviar no Enter, fechar no Escape, navegar com as teclas de seta ou registrar atalhos de teclado globais.
"use client";
import { useState, useEffect, useRef, useCallback } from "react";
type SearchResult = { id: string; title: string };
const MOCK_RESULTS: SearchResult[] = [
{ id: "1", title: "Getting Started with React" },
{ id: "2", title: "React Hooks in Depth" },
{ id: "3", title: "Server Components Explained" },
{ id: "4", title: "TypeScript with React" },
{ id: "5", title: "React Performance Patterns" },
];
export default function SearchWithShortcuts() {
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const results = query
? MOCK_RESULTS.filter((r) =>
r.title.toLowerCase().includes(query.toLowerCase())
)
: [];
// Atalho de teclado global: Cmd+K para abrir a busca
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
setIsOpen(true);
// Foca após a atualização do estado renderizar o input
setTimeout(() => inputRef.current?.focus(), 0);
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setSelectedIndex((i) => Math.min(i + 1, results.length - 1));
break;
case "ArrowUp":
e.preventDefault();
setSelectedIndex((i) => Math.max(i - 1, 0));
break;
case "Enter":
e.preventDefault();
if (results[selectedIndex]) {
console.log("Selecionado:", results[selectedIndex].title);
setIsOpen(false);
setQuery("");
}
break;
case "Escape":
setIsOpen(false);
setQuery("");
inputRef.current?.blur();
break;
}
},
[results, selectedIndex]
);
if (!isOpen) {
return (
<button
onClick={() => {
setIsOpen(true);
setTimeout(() => inputRef.current?.focus(), 0);
}}
style={{
padding: "8px 16px",
border: "1px solid #e5e7eb",
borderRadius: "8px",
background: "#fff",
cursor: "pointer",
}}
>
Buscar...{" "}
<kbd style={{ color: "#9ca3af", fontSize: "0.85em" }}>Cmd+K</kbd>
</button>
);
}
return (
<div
style={{
border: "1px solid #e5e7eb",
borderRadius: "12px",
overflow: "hidden",
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
width: 400,
}}
>
<input
ref={inputRef}
value={query}
onChange={(e) => {
setQuery(e.target.value);
setSelectedIndex(0);
}}
onKeyDown={handleKeyDown}
placeholder="Digite para buscar... (Esc para fechar)"
style={{
width: "100%",
padding: "12px 16px",
border: "none",
outline: "none",
fontSize: "16px",
boxSizing: "border-box",
}}
/>
{results.length > 0 && (
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
{results.map((result, index) => (
<li
key={result.id}
style={{
padding: "10px 16px",
background: index === selectedIndex ? "#f3f4f6" : "#fff",
cursor: "pointer",
}}
onClick={() => {
console.log("Selecionado:", result.title);
setIsOpen(false);
setQuery("");
}}
>
{result.title}
</li>
))}
</ul>
)}
{query && results.length === 0 && (
<p style={{ padding: "10px 16px", color: "#9ca3af", margin: 0 }}>
Nenhum resultado encontrado.
</p>
)}
</div>
);
}O que isso demonstra:
Cmd+K) usando useEffect com document.addEventListeneronKeyDown local para navegação por seta, Enter para selecionar e Escape para fecharselectedIndex para navegação de lista controlada por tecladouseEffectKeyboardEvent nativo em um Evento Sintético. O manipulador recebe um React.KeyboardEvent<T> com todas as propriedades padrão: key, code, altKey, ctrlKey, metaKey, shiftKey, repeat.onKeyDown dispara quando uma tecla é pressionada. Ele dispara repetidamente se a tecla for mantida pressionada (a propriedade repeat é true nos disparos subsequentes).onKeyUp dispara uma vez quando a tecla é liberada. Use-o quando precisar da ação apenas após a tecla ser totalmente pressionada e liberada.onKeyPress é depreciado e removido da especificação DOM. Ele não dispara para teclas que não são de caractere (Escape, Seta, Ctrl, etc.). Sempre use onKeyDown em vez disso.document dentro de um useEffect.Manipulador de tecla única com e.key:
function KeyLogger() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
console.log(`Tecla: ${e.key}, Código: ${e.code}, Repetir: ${e.repeat}`);
};
return (
<div tabIndex={0} onKeyDown={handleKeyDown}>
Foque em mim e pressione qualquer tecla
</div>
);
}Combinações de teclas modificadoras (Ctrl/Cmd+S para salvar):
function SaveHandler({ onSave }: { onSave: () => void }) {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
const isMac = navigator.userAgent.includes("Mac");
const modifier = isMac ? e.metaKey : e.ctrlKey;
if (modifier && e.key === "s") {
e.preventDefault();
onSave();
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [onSave]);
return null;
}Navegação por seta em uma lista:
function ArrowNavList({ items }: { items: string[] }) {
const [activeIndex, setActiveIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent<HTMLUListElement>) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setActiveIndex((i) => (i + 1) % items.length);
break;
case "ArrowUp":
e.preventDefault();
setActiveIndex((i) => (i - 1 + items.length) % items.length);
break;
case "Home":
e.preventDefault();
setActiveIndex(0);
break;
case "End":
e.preventDefault();
setActiveIndex(items.length - 1);
break;
}
};
return (
<ul tabIndex={0} onKeyDown={handleKeyDown} role="listbox">
{items.map((item, i) => (
<li
key={item}
role="option"
aria-selected={i === activeIndex}
style={{ background: i === activeIndex ? "#e0e7ff" : "transparent" }}
>
{item}
</li>
))}
</ul>
);
}Atalhos de teclado globais com um hook:
function useKeyboardShortcut(
key: string,
callback: () => void,
options: { ctrl?: boolean; meta?: boolean; shift?: boolean } = {}
) {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (options.ctrl && !e.ctrlKey) return;
if (options.meta && !e.metaKey) return;
if (options.shift && !e.shiftKey) return;
if (e.key.toLowerCase() !== key.toLowerCase()) return;
e.preventDefault();
callback();
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [key, callback, options.ctrl, options.meta, options.shift]);
}
// Uso
function App() {
useKeyboardShortcut("k", () => openSearch(), { meta: true });
useKeyboardShortcut("/", () => openSearch());
useKeyboardShortcut("Escape", () => closeModal());
return <div>...</div>;
}Prevenindo atalhos de navegador padrão:
function PreventBrowserDefault() {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
// Previne Ctrl+P (imprimir) para abrir o diálogo de impressão personalizado
if ((e.metaKey || e.ctrlKey) && e.key === "p") {
e.preventDefault();
console.log("Diálogo de impressão personalizado");
}
// Previne Ctrl+F (buscar) para abrir a busca personalizada
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
e.preventDefault();
console.log("Busca personalizada");
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
return <div>Atalhos do navegador substituídos</div>;
}// React.KeyboardEvent<T> -- T é o tipo do elemento
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.currentTarget; // HTMLInputElement
e.key; // "Enter", "Escape", "a", "ArrowDown", etc.
e.code; // "Enter", "Escape", "KeyA", "ArrowDown", etc.
};
// e.key vs e.code
// e.key: o caractere produzido ("a", "A", "/", "Enter")
// -- afetado pelo layout do teclado e teclas modificadoras
// e.code: a tecla física ("KeyA", "Slash", "Enter")
// -- consistente independentemente do layout
// Propriedades de teclas modificadoras (todas booleanas)
// e.altKey -- Alt (Option no Mac)
// e.ctrlKey -- Control
// e.metaKey -- Cmd no Mac, tecla Windows no Windows
// e.shiftKey -- Shift
// e.repeat -- true se a tecla for mantida pressionada
// Native KeyboardEvent (para listeners de useEffect)
useEffect(() => {
const handler = (e: KeyboardEvent) => {
// Este é o KeyboardEvent DOM nativo, NÃO React.KeyboardEvent
// Mesmas propriedades, mas tipo diferente
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
// Padrão comum: mapeamento de manipuladores de teclas
type KeyHandlerMap = Record<string, (e: React.KeyboardEvent) => void>;
const handlers: KeyHandlerMap = {
Enter: (e) => submit(),
Escape: (e) => close(),
ArrowDown: (e) => { e.preventDefault(); moveDown(); },
ArrowUp: (e) => { e.preventDefault(); moveUp(); },
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
handlers[e.key]?.(e);
};onKeyPress está depreciado -- Ele não dispara para teclas que não são de caractere (Escape, setas, teclas de função, modificadores). Foi removido da especificação DOM. Correção: Sempre use onKeyDown em vez disso. A única razão para usar onKeyUp é quando você especificamente precisa da ação ao liberar a tecla.
Valores de e.key diferem de e.code -- e.key retorna "a" ou "A" dependendo do Shift; e.code sempre retorna "KeyA". Para atalhos de teclado, use e.key com .toLowerCase() para corresponder independentemente do caso. Para controles de jogo ou posição física da tecla, use e.code.
Eventos de teclado exigem foco -- Um <div> não recebe eventos de teclado a menos que tenha tabIndex={0} (ou -1 apenas para foco programático). Correção: Adicione tabIndex a elementos não interativos que precisam de manipuladores de teclado, ou use document.addEventListener global para atalhos que devem funcionar independentemente do foco.
Atalhos globais vazam entre componentes -- Se você registrar Cmd+K em um useEffect mas o componente for desmontado, o listener persiste e dispara em um closure obsoleto. Correção: Sempre retorne uma função de limpeza de useEffect que remova o listener.
e.repeat dispara continuamente quando uma tecla é mantida pressionada -- Manter uma tecla pressionada dispara onKeyDown repetidamente. Se o seu manipulador realizar uma ação cara (como uma chamada de API), ele disparará dezenas de vezes. Correção: Verifique if (e.repeat) return; no topo do seu manipulador se você quiser apenas o primeiro pressionamento.
Substituir atalhos do navegador é não confiável -- Alguns atalhos do navegador (como Ctrl+T, Ctrl+W, Ctrl+N) não podem ser interceptados por JavaScript porque o navegador os manipula antes que seu código seja executado. Correção: Substitua apenas atalhos que você sabe que são interceptáveis (Ctrl+S, Ctrl+P, Ctrl+F, etc.) e teste em diferentes navegadores.
Comparar e.key com literais de string é frágil para teclas especiais -- Os valores de key como "Enter", "Escape", "ArrowDown" diferenciam maiúsculas de minúsculas e seguem a especificação UI Events. Não compare com "enter" ou "esc". Correção: Use os valores exatos da especificação: "Enter", "Escape", "ArrowDown", "ArrowUp", "Tab", etc.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Atributo HTML accessKey | Ativação simples de uma tecla para botões ou links | Você precisa de combinações de modificadores ou lógica complexa |
document.addEventListener em useEffect | Atalhos globais que funcionam independentemente do elemento focado | O atalho está restrito a um input ou componente específico |
| Bibliotecas de terceiros (react-hotkeys-hook, tinykeys) | Muitos atalhos, sequências de acordes ou gerenciamento de escopo | Você tem um ou dois atalhos simples |
| Padrões de teclado ARIA | Navegação de widgets compostos (menus, abas, listboxes) | Inputs de formulário simples que manipulam teclas nativamente |
contentEditable | Edição de texto rico com controle total do teclado | Inputs de formulário padrão ou navegação |
onKeyDown como seu evento de teclado principal -- ele dispara para todas as teclas, incluindo teclas que não são de caractereonKeyUp apenas quando precisar da ação ao liberar a teclaonKeyPress -- ele está depreciado e não dispara para Escape, setas ou teclas modificadorase.key retorna o caractere produzido ("a", "A", "Enter") e é afetado pelo layout do teclado e modificadorese.code retorna a tecla física ("KeyA", "Enter") e é consistente independentemente do layoute.key para atalhos; use e.code para controles de jogo ou posição física da teclauseEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
openSearch();
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);Eventos de teclado só disparam em elementos focados. Um <div> não é focável por padrão. Adicione tabIndex={0} para torná-lo navegável pelo teclado, ou tabIndex={-1} apenas para foco programático. Alternativamente, use document.addEventListener para atalhos globais.
onKeyDown dispara repetidamente quando uma tecla é mantida pressionada. A propriedade e.repeat é true nos disparos subsequentes. Se o seu manipulador fizer algo caro (como uma chamada de API), adicione if (e.repeat) return; no topo para lidar apenas com o primeiro pressionamento.
const handleKeyDown = (e: React.KeyboardEvent<HTMLUListElement>) => {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setIndex((i) => (i + 1) % items.length);
break;
case "ArrowUp":
e.preventDefault();
setIndex((i) => (i - 1 + items.length) % items.length);
break;
}
};
return <ul tabIndex={0} onKeyDown={handleKeyDown} role="listbox">...</ul>;Se você registrar um document.addEventListener em useEffect mas esquecer a função de limpeza, o listener persiste após o desmontamento e dispara em um closure obsoleto. Sempre retorne uma função de limpeza: return () => document.removeEventListener("keydown", handler);.
Não. Alguns atalhos (Ctrl+T, Ctrl+W, Ctrl+N) são manipulados pelo navegador antes que seu código seja executado e não podem ser interceptados. Substitua apenas atalhos que você sabe que são interceptáveis (Ctrl+S, Ctrl+P, Ctrl+F) e teste em diferentes navegadores.
const handler = (e: KeyboardEvent) => {
const isMac = navigator.userAgent.includes("Mac");
const modifier = isMac ? e.metaKey : e.ctrlKey;
if (modifier && e.key === "s") {
e.preventDefault();
save();
}
};Sim. Valores de tecla como "Enter", "Escape", "ArrowDown" seguem a especificação UI Events e diferenciam maiúsculas de minúsculas. Comparar com "enter" ou "esc" não corresponderá. Sempre use os valores exatos da especificação.
React.KeyboardEvent<T> é o tipo de evento sintético usado em manipuladores JSX (por exemplo, onKeyDown)KeyboardEvent (sem prefixo React) é o tipo DOM nativo usado em useEffect com document.addEventListenertype KeyHandlerMap = Record<string, (e: React.KeyboardEvent) => void>;
const handlers: KeyHandlerMap = {
Enter: () => submit(),
Escape: () => close(),
ArrowDown: (e) => { e.preventDefault(); moveDown(); },
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
handlers[e.key]?.(e);
};Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥