
Eventos de teclado en JavaScript: key, code, keyCode y cómo depurarlos
📷 Life Of Pix / PexelsEventos de teclado en JavaScript: key, code, keyCode y cómo depurarlos
Una guía práctica sobre los eventos de teclado en JavaScript — por qué keyCode está obsoleto, en qué se diferencian key y code, y cómo crear atajos de teclado confiables que funcionen en todos los navegadores y distribuciones de teclado.
Este bug lo he encontrado más de una vez: escribo un manejador de atajos de teclado en Chrome, todo funciona perfectamente, lo despliego y luego recibo un reporte de error de alguien usando Firefox. Mismo código, comportamiento diferente. Después de una hora investigando, encuentro al culpable — estaba usando e.keyCode para detectar la tecla, y retornaba valores diferentes según el navegador y la distribución del teclado.
Ese bug es completamente evitable, y por eso vale la pena entender bien los eventos de teclado en JavaScript. Este artículo recorre el modelo completo de eventos de teclado — qué eventos se disparan, qué propiedades usar, cuáles evitar y cómo construir atajos que realmente funcionen en todos lados.
Los tres eventos: keydown, keypress, keyup
Cuando un usuario presiona una tecla, el navegador dispara hasta tres eventos en secuencia:
- keydown — se dispara inmediatamente al presionar la tecla, antes de que se inserte algún carácter
- keypress — se dispara después de keydown, pero solo para teclas que producen un valor de carácter
- keyup — se dispara cuando la tecla es liberada
En resumen: usa keydown para la mayoría de los casos. Aquí está el porqué:
keypress está obsoleto. Nunca se disparó para teclas no imprimibles como Escape, Delete, F1-F12 o las teclas de flecha. Si tu manejador de atajos solo funcionaba para teclas de letras, keypress probablemente era la razón. No lo uses en código nuevo.
keyup es útil cuando específicamente quieres reaccionar después de que se libera la tecla — como actualizar una vista previa después de que el usuario termina de escribir un carácter. Pero para atajos y controles de juegos, keydown proporciona un comportamiento más rápido y predecible.
keydown también tiene la ventaja de que se dispara repetidamente cuando se mantiene presionada una tecla (a la velocidad de repetición del teclado del sistema operativo), que es exactamente lo que quieres para cosas como el desplazamiento o mover un personaje de juego.
document.addEventListener('keydown', (e) => {
// Aquí es donde quieres estar
console.log(e.key, e.code);
});
Las propiedades que realmente importan
Cuando se dispara un KeyboardEvent, el objeto de evento tiene muchas propiedades. Veamos las que necesitas conocer.
key — Lo que produce la tecla
key da el valor de cadena de lo que la tecla representa en el contexto actual. Para una tecla de letra normal, es el carácter: "a", "A" (con Shift), "é" (en un teclado francés). Para teclas especiales, es un nombre descriptivo: "Enter", "Escape", "ArrowLeft", "F5", "Backspace".
Esta es la propiedad a la que debes acudir primero. Es consciente de la distribución y los modificadores, y te dice lo que la tecla significa para el usuario.
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
submitForm();
}
if (e.key === 'Escape') {
closeModal();
}
if (e.key === 'ArrowLeft') {
goToPreviousSlide();
}
});
Un detalle importante: key distingue mayúsculas y minúsculas y es sensible a los modificadores. Presionar a sin Shift da "a". Con Shift, da "A". Si tu atajo no debe depender de la capitalización, normaliza:
if (e.key.toLowerCase() === 'k') {
openCommandPalette();
}
code — Qué tecla física fue presionada
code da el identificador para la posición física de la tecla en el teclado, independientemente de qué teclas modificadoras se mantengan presionadas o qué distribución de teclado tenga el usuario. Presionar la tecla en la esquina superior izquierda del área de letras siempre es "KeyQ" — incluso si la distribución del usuario coloca "A" allí (como en una distribución AZERTY).
El formato es consistente: las teclas de letras son "KeyA" a "KeyZ", las teclas de dígitos son "Digit0" a "Digit9", las teclas de función son "F1" a "F12".
Usa code cuando te importa la ubicación física de la tecla en lugar de lo que produce. El caso de uso clásico son los controles de juego:
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW':
case 'ArrowUp':
player.moveUp();
break;
case 'KeyS':
case 'ArrowDown':
player.moveDown();
break;
case 'KeyA':
case 'ArrowLeft':
player.moveLeft();
break;
case 'KeyD':
case 'ArrowRight':
player.moveRight();
break;
}
});
Usar code significa que los controles WASD funcionan igual independientemente de la distribución de teclado del jugador. En AZERTY, la tecla W está en una posición diferente, por lo que e.key daría "z" — pero e.code sigue dando "KeyW" porque es la misma posición física.
keyCode y which — Heredado, obsoleto, evítalos
keyCode y which son la forma antigua. Dan un código numérico para la tecla — 65 para A, 13 para Enter, 27 para Escape. El problema es que estos valores no eran consistentes, especialmente para puntuación y teclas especiales. Diferentes navegadores devolvían números diferentes, y los valores se basaban en códigos de teclas virtuales de Windows de maneras que tenían cero sentido intuitivo.
La documentación de MDN marca tanto keyCode como which como obsoletos. Siguen existiendo por compatibilidad con versiones anteriores, pero no los uses en código nuevo.
Código antiguo que podrías ver:
// No hagas esto
if (e.keyCode === 13) { /* Enter */ }
if (e.which === 27) { /* Escape */ }
Equivalente moderno:
// Haz esto en su lugar
if (e.key === 'Enter') { /* Enter */ }
if (e.key === 'Escape') { /* Escape */ }
charCode — Solo en keypress, también obsoleto
charCode era el punto de código Unicode del carácter, pero solo funcionaba en eventos keypress, y solo para caracteres imprimibles. Como keypress en sí está obsoleto, charCode está doblemente obsoleto. Simplemente usa e.key y llama a .codePointAt(0) si necesitas el valor Unicode.
location — Para teclas que aparecen en múltiples lugares
location indica qué instancia de una tecla fue presionada, para teclas que aparecen en múltiples lugares del teclado. Es un número: 0 es la posición estándar, 1 es el modificador izquierdo, 2 es el modificador derecho y 3 es el teclado numérico.
e.location === 1 con e.key === "Shift" significa que se presionó la tecla Shift izquierda. e.location === 3 con e.key === "5" significa que se presionó el 5 del teclado numérico. Raramente se necesita, pero es bueno saber que existe.
Teclas modificadoras: Ctrl, Shift, Alt, Meta
Para los atajos de teclado, generalmente combinas una tecla regular con uno o más modificadores. El objeto de evento tiene propiedades booleanas para cada modificador:
e.ctrlKey— Ctrl está presionadoe.shiftKey— Shift está presionadoe.altKey— Alt (u Option en Mac) está presionadoe.metaKey— Meta está presionado (Command en Mac, tecla Windows en Windows)
document.addEventListener('keydown', (e) => {
// Ctrl+S (o Cmd+S en Mac)
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault(); // Previene el diálogo Guardar del navegador
saveDocument();
}
// Ctrl+Shift+P — paleta de comandos
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'p') {
e.preventDefault();
openCommandPalette();
}
// Alt+Left — ir atrás
if (e.altKey && e.key === 'ArrowLeft') {
goBack();
}
});
El patrón e.ctrlKey || e.metaKey es el enfoque estándar para atajos multiplataforma. En macOS, la mayoría de los atajos usan Command (metaKey). En Windows/Linux, usan Ctrl. Este patrón maneja ambos.
También nota el e.preventDefault(). Si tu atajo entra en conflicto con un atajo del navegador, necesitas llamar esto para evitar que el navegador actúe sobre él. Sin esto, Ctrl+S abriría el diálogo de guardar del navegador.
El problema de los teclados internacionales
Aquí hay un escenario que atrapa a muchos desarrolladores: tu aplicación tiene un atajo de teclado, digamos Ctrl+/ para alternar comentarios. Lo pruebas en tu teclado en-US, funciona perfecto. Un usuario en México manda un reporte de bug — el atajo no funciona.
En muchas distribuciones de teclado latinoamericanas, el carácter / no está en una tecla dedicada. Se accede mediante Shift+7 u otra combinación diferente. Entonces e.key al presionar esa posición de tecla es completamente diferente de lo que esperarías.
Dos enfoques para manejar esto:
Opción 1: Usar e.code para atajos basados en posición
// La tecla / en un teclado US está en una posición física específica
// code siempre dará 'Slash' para esa posición
if (e.ctrlKey && e.code === 'Slash') {
toggleComment();
}
Funciona si el atajo tiene sentido basándose en la posición de la tecla. Pero puede sentirse contraintuitivo para usuarios en otras distribuciones.
Opción 2: Dejar que los usuarios configuren sus propios atajos
Apps como VS Code hacen esto bien — te dejan reasignar cualquier atajo a la combinación de teclas que prefieras. Más trabajo de implementar, pero la respuesta correcta para una herramienta profesional con una base de usuarios internacional.
Accesibilidad: la navegación por teclado importa
Si estás añadiendo interacciones de teclado personalizadas a tu aplicación, no olvides que la navegación por teclado es un requisito fundamental de accesibilidad. Los usuarios que dependen de lectores de pantalla o que no pueden usar un ratón dependen del acceso por teclado.
Algunas cosas a tener en cuenta:
Gestión del foco. Asegúrate de que los elementos interactivos sean enfocables (tabindex="0" si es necesario) y que el foco se mueva lógicamente a medida que la interfaz cambia. Cuando abres un modal, mueve el foco dentro de él. Cuando lo cierras, devuelve el foco al elemento que lo activó.
No atrapes el foco (excepto en modales). Los usuarios deben poder navegar por tu interfaz con Tab sin quedarse atrapados.
No dependas únicamente de los atajos de teclado. Los usuarios podrían no descubrirlos. Proporciona alternativas de interfaz visibles para cada acción.
Depuración con el Keycode Viewer
Cuando estás construyendo interacciones de teclado, la parte más difícil a menudo es simplemente averiguar qué valores te está dando el navegador para una pulsación de tecla específica. La documentación es excelente, pero a veces solo necesitas presionar la tecla y ver el resultado.
Eso es exactamente lo que hace nuestra herramienta Keycode Viewer. Ábrela, presiona cualquier tecla, e inmediatamente muestra:
key— el valor lógicocode— el identificador de posición físicakeyCode— el valor numérico heredado (para referencia al trabajar con código antiguo)which— la otra propiedad heredadacharCode— el código de carácter de los eventos keypresslocation— estándar/izquierda/derecha/teclado numéricoctrlKey,shiftKey,altKey,metaKey— estados de los modificadores
Es especialmente útil cuando trabajas con teclados internacionales, caracteres especiales, o estás tratando de averiguar por qué el código existente no reconoce una tecla particular. En lugar de agregar declaraciones console.log y recargar, simplemente abres la herramienta y presionas la tecla.
Referencia rápida
| Propiedad | Usar cuando necesitas | ¿Obsoleto? |
|---|---|---|
key | El nombre del carácter o acción | No |
code | La posición física de la tecla | No |
keyCode | Nunca — solo legado | Sí |
which | Nunca — solo legado | Sí |
charCode | Nunca — solo legado | Sí |
ctrlKey | Verificar si Ctrl está presionado | No |
shiftKey | Verificar si Shift está presionado | No |
altKey | Verificar si Alt/Option está presionado | No |
metaKey | Verificar si Cmd/Win está presionado | No |
El modelo mental principal: ve primero a e.key. Te dice lo que significa la tecla. Solo usa e.code cuando específicamente te importa la posición física (controles de juego, atajos independientes de la distribución). Nunca uses keyCode, which o charCode en código nuevo.
Maneja esto correctamente y tus interacciones de teclado serán consistentes en todos los navegadores, distribuciones de teclado y sistemas operativos. Y dejarás de recibir esos reportes de bug de usuarios de Firefox.