ToolPal
Primer plano de un teclado de computadora

Eventos de teclado en JavaScript: key, code, keyCode y cómo depurarlos

📷 Life Of Pix / Pexels

Eventos 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.

9 de abril de 202610 min de lectura

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:

  1. keydown — se dispara inmediatamente al presionar la tecla, antes de que se inserte algún carácter
  2. keypress — se dispara después de keydown, pero solo para teclas que producen un valor de carácter
  3. 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á presionado
  • e.shiftKey — Shift está presionado
  • e.altKey — Alt (u Option en Mac) está presionado
  • e.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ógico
  • code — el identificador de posición física
  • keyCode — el valor numérico heredado (para referencia al trabajar con código antiguo)
  • which — la otra propiedad heredada
  • charCode — el código de carácter de los eventos keypress
  • location — estándar/izquierda/derecha/teclado numérico
  • ctrlKey, 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

PropiedadUsar cuando necesitas¿Obsoleto?
keyEl nombre del carácter o acciónNo
codeLa posición física de la teclaNo
keyCodeNunca — solo legado
whichNunca — solo legado
charCodeNunca — solo legado
ctrlKeyVerificar si Ctrl está presionadoNo
shiftKeyVerificar si Shift está presionadoNo
altKeyVerificar si Alt/Option está presionadoNo
metaKeyVerificar si Cmd/Win está presionadoNo

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.

Preguntas Frecuentes

Compartir

XLinkedIn

Publicaciones relacionadas