ToolPal
컴퓨터 키보드 클로즈업

JavaScript 키보드 이벤트 완벽 가이드: key, code, keyCode의 차이와 디버깅 방법

📷 Life Of Pix / Pexels

JavaScript 키보드 이벤트 완벽 가이드: key, code, keyCode의 차이와 디버깅 방법

JavaScript 키보드 이벤트의 핵심 개념 — keyCode가 왜 deprecated됐는지, key와 code의 차이는 무엇인지, 모든 브라우저와 키보드 레이아웃에서 동작하는 단축키를 만드는 방법을 실전 예제로 설명합니다.

2026년 4월 9일8분 소요

비슷한 버그를 여러 번 겪어봤습니다. Chrome에서 키보드 단축키 핸들러를 작성하고, 잘 동작하는 걸 확인한 뒤 배포했는데, 얼마 지나지 않아 Firefox에서 동작하지 않는다는 버그 리포트가 들어왔습니다. 같은 코드인데 동작이 달랐습니다. 한 시간쯤 파고들어 보니 원인은 단순했습니다 — 키를 감지할 때 e.keyCode를 사용했는데, 브라우저와 키보드 레이아웃에 따라 다른 값을 반환하고 있었습니다.

이 버그는 완전히 예방 가능합니다. 그래서 JavaScript 키보드 이벤트를 제대로 이해하는 게 중요합니다. 이 글에서는 전체 키보드 이벤트 모델을 살펴봅니다 — 어떤 이벤트가 발생하는지, 어떤 속성을 사용해야 하는지, 어떤 건 피해야 하는지, 그리고 어디서나 동작하는 단축키를 만드는 방법까지 다룹니다.

세 가지 이벤트: keydown, keypress, keyup

사용자가 키를 누르면 브라우저는 순서대로 최대 세 가지 이벤트를 발생시킵니다:

  1. keydown — 키를 누르는 즉시 발생, 문자가 삽입되기 전
  2. keypress — keydown 이후 발생, 문자를 생성하는 키에만 해당 (문자, 숫자, 구두점)
  3. keyup — 키를 떼면 발생

짧게 말하면: 대부분의 경우 keydown을 사용하세요. 이유는 이렇습니다:

keypress는 deprecated됐습니다. Escape, Delete, F1~F12, 방향키 같은 출력되지 않는 키에서는 발생하지 않았습니다. 단축키 핸들러가 문자 키에만 동작했다면, keypress가 원인이었을 가능성이 높습니다. 새 코드에서는 사용하지 마세요.

keyup은 키를 뗀 뒤에 반응해야 할 때 유용합니다 — 예를 들어 사용자가 문자 입력을 마친 후 미리보기를 갱신하는 경우. 하지만 단축키나 게임 컨트롤에는 keydown이 더 빠르고 예측 가능한 동작을 제공합니다.

keydown은 키를 계속 누르고 있을 때 OS 키 반복 속도로 반복 발생한다는 장점도 있습니다. 스크롤이나 게임 캐릭터 이동에 필요한 동작 방식입니다.

document.addEventListener('keydown', (e) => {
  // 여기가 맞는 위치입니다
  console.log(e.key, e.code);
});

실제로 중요한 속성들

KeyboardEvent가 발생하면 이벤트 객체에는 여러 속성이 있습니다. 꼭 알아야 할 것들을 설명합니다.

key — 키가 생성하는 값

key는 현재 컨텍스트에서 키가 나타내는 문자열 값을 반환합니다. 일반 문자 키라면 문자 자체입니다: "a", "A" (Shift와 함께), "é" (프랑스어 키보드). 특수 키라면 설명 이름입니다: "Enter", "Escape", "ArrowLeft", "F5", "Backspace".

가장 먼저 사용해야 할 속성입니다. 레이아웃과 수정자를 인식하며, 키가 사용자에게 무엇을 의미하는지 알려줍니다.

document.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    submitForm();
  }
  if (e.key === 'Escape') {
    closeModal();
  }
  if (e.key === 'ArrowLeft') {
    goToPreviousSlide();
  }
});

한 가지 주의점: key는 대소문자와 수정자를 구분합니다. Shift 없이 a 키를 누르면 "a", Shift와 함께 누르면 "A"입니다. 대소문자에 무관한 단축키를 만들려면 정규화하세요:

if (e.key.toLowerCase() === 'k') {
  openCommandPalette();
}

code — 어떤 물리적 키가 눌렸는지

code는 수정자 키나 키보드 레이아웃에 상관없이 물리적 키 위치의 식별자를 반환합니다. 글자 영역 왼쪽 상단의 키는 항상 "KeyQ"입니다 — 사용자의 레이아웃이 거기에 "A"를 배치해도 마찬가지입니다 (AZERTY 레이아웃처럼).

형식은 일관됩니다: 문자 키는 "KeyA" ~ "KeyZ", 숫자 키는 "Digit0" ~ "Digit9", 기능 키는 "F1" ~ "F12".

키의 위치가 생산하는 문자보다 중요할 때 code를 사용합니다. 대표적인 예가 게임 컨트롤입니다:

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;
  }
});

code를 사용하면 QWERTY, AZERTY, Dvorak 어떤 레이아웃이든 WASD 컨트롤이 동일하게 동작합니다. AZERTY에서는 W 키가 다른 위치에 있어 e.key"z"를 반환하지만, e.code는 여전히 물리적 위치가 같기 때문에 "KeyW"를 반환합니다.

keyCodewhich — 레거시, deprecated, 피하세요

keyCodewhich는 과거 방식입니다. 키에 대한 숫자 코드를 반환합니다 — A65, Enter는 13, Escape는 27. 문제는 특히 구두점과 특수 키에서 이 값들이 일관성이 없었다는 것입니다. 브라우저마다 다른 숫자를 반환했고, 값들은 직관성이 전혀 없는 Windows 가상 키 코드 기반이었습니다.

MDN 문서는 keyCodewhich 모두 deprecated로 표시합니다. 하위 호환성을 위해 아직 존재하지만, 새 코드에서는 사용하지 마세요. 이 속성을 사용하는 기존 코드를 유지보수 중이라면 마이그레이션을 계획하세요.

흔히 보는 과거 코드:

// 하지 마세요
if (e.keyCode === 13) { /* Enter */ }
if (e.which === 27) { /* Escape */ }

현대적인 대체:

// 이렇게 하세요
if (e.key === 'Enter') { /* Enter */ }
if (e.key === 'Escape') { /* Escape */ }

charCode — keypress에서만, 역시 deprecated

charCode는 문자의 Unicode 코드 포인트를 반환했지만, keypress 이벤트에서만, 그것도 출력 가능한 문자에서만 동작했습니다. keypress 자체가 deprecated됐으므로 charCode도 이중으로 deprecated입니다. e.key를 사용하고 Unicode 값이 필요하면 .codePointAt(0)을 호출하세요.

location — 여러 위치에 있는 키 구분

location은 키보드의 여러 위치에 존재하는 키가 어디서 눌렸는지 알려줍니다. 숫자값입니다: 0은 표준 위치, 1은 왼쪽 수정자, 2는 오른쪽 수정자, 3은 넘패드.

e.location === 1e.key === "Shift"면 왼쪽 Shift 키가 눌린 것입니다. e.location === 3e.key === "5"면 넘패드의 5가 눌린 것입니다. 잘 필요하지 않지만 알아두면 좋습니다.

수정자 키: Ctrl, Shift, Alt, Meta

키보드 단축키는 보통 일반 키와 하나 이상의 수정자 키를 조합합니다. 이벤트 객체에는 각 수정자에 대한 불리언 속성이 있습니다:

  • e.ctrlKey — Ctrl 키가 눌린 상태
  • e.shiftKey — Shift 키가 눌린 상태
  • e.altKey — Alt(Mac에서는 Option) 키가 눌린 상태
  • e.metaKey — Meta 키가 눌린 상태 (Mac에서는 Command, Windows에서는 Windows 키)

이 속성들은 어떤 속성으로 키를 식별하든 상관없이 현재 이벤트에 대해 항상 정확하게 설정됩니다:

document.addEventListener('keydown', (e) => {
  // Ctrl+S (Mac에서는 Cmd+S)
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault(); // 브라우저의 저장 대화상자 방지
    saveDocument();
  }

  // Ctrl+Shift+P — 명령 팔레트
  if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'p') {
    e.preventDefault();
    openCommandPalette();
  }

  // Alt+Left — 뒤로 가기
  if (e.altKey && e.key === 'ArrowLeft') {
    goBack();
  }
});

e.ctrlKey || e.metaKey 패턴은 크로스플랫폼 단축키의 표준 접근 방식입니다. macOS에서는 대부분 Command(metaKey)를 사용하고, Windows/Linux에서는 Ctrl을 사용합니다. 이 패턴이 둘 다 처리합니다.

e.preventDefault() 호출도 중요합니다. 단축키가 브라우저 단축키와 충돌할 경우 브라우저가 해당 동작을 실행하는 것을 막으려면 이 메서드를 호출해야 합니다. 없으면 Ctrl+S가 브라우저의 저장 대화상자를 열어버립니다.

국제 키보드 문제

개발자를 자주 당황시키는 시나리오가 있습니다: 앱에 키보드 단축키(예: 주석 토글용 Ctrl+/)를 추가합니다. en-US 키보드에서 테스트하면 잘 동작합니다. 그런데 독일의 사용자가 단축키가 동작하지 않는다는 버그 리포트를 올립니다.

독일어 키보드 레이아웃에는 / 문자를 위한 전용 키가 없습니다. Shift+7 같은 다른 조합으로 입력합니다. 그래서 해당 키 위치를 눌렀을 때 e.key는 예상과 전혀 다른 값을 반환합니다.

두 가지 접근 방식이 있습니다:

방법 1: 위치 기반 단축키에는 e.code 사용

// US 키보드의 / 키는 특정 물리적 위치에 있습니다
// code는 항상 해당 위치에 대해 'Slash'를 반환합니다
if (e.ctrlKey && e.code === 'Slash') {
  toggleComment();
}

단축키가 키 위치 기반으로 의미가 있다면 잘 동작합니다. 하지만 다른 레이아웃 사용자에게는 키에 표시된 내용이 예상과 다르게 느껴질 수 있습니다.

방법 2: 사용자가 단축키를 직접 설정하도록 허용

VS Code 같은 앱이 잘 구현한 방식입니다 — 모든 단축키를 원하는 조합으로 다시 지정할 수 있게 합니다. 구현하기 복잡하지만, 국제 사용자를 위한 전문 도구에서는 올바른 답입니다.

방법 3: 여러 키 값 허용

// US 레이아웃 값과 일반적인 대안 모두 허용
const toggleKeys = new Set(['/', '?', '-']);
if (e.ctrlKey && toggleKeys.has(e.key)) {
  toggleComment();
}

이상적이진 않지만 단순한 경우에는 실용적입니다.

접근성: 키보드 탐색은 중요합니다

앱에 커스텀 키보드 인터랙션을 추가할 때 키보드 탐색이 핵심 접근성 요구사항임을 잊지 마세요. 스크린 리더에 의존하거나 마우스를 사용할 수 없는 사용자는 키보드 접근에 의존합니다.

몇 가지 사항을 기억하세요:

포커스 관리. 인터랙티브 요소가 포커스 가능한지 확인하고(tabindex="0" 필요 시), UI 변경에 따라 포커스가 논리적으로 이동하는지 확인하세요. 모달을 열면 포커스를 모달 안으로 이동하고, 닫으면 모달을 열었던 요소로 돌아와야 합니다.

포커스를 가두지 마세요 (모달 제외). 사용자가 Tab 키로 인터페이스를 탐색할 때 막히지 않아야 합니다.

키보드 단축키에만 의존하지 마세요. 사용자가 발견하지 못할 수 있습니다. 모든 액션에 대해 보이는 UI 대안을 제공하세요.

Keycode Viewer로 디버깅하기

키보드 인터랙션을 구현할 때 가장 어려운 부분 중 하나는 특정 키 입력에 대해 브라우저가 어떤 값을 반환하는지 파악하는 것입니다. 문서가 도움이 되지만, 직접 키를 눌러 결과를 확인하는 게 필요할 때가 있습니다.

Keycode Viewer 도구가 바로 그 역할을 합니다. 도구를 열고 아무 키나 누르면 즉시 확인할 수 있습니다:

  • key — 논리적 값
  • code — 물리적 위치 식별자
  • keyCode — 레거시 숫자 값 (기존 코드 작업 시 참고용)
  • which — 다른 레거시 속성
  • charCode — keypress 이벤트의 문자 코드
  • location — 표준/왼쪽/오른쪽/넘패드
  • ctrlKey, shiftKey, altKey, metaKey — 수정자 상태

국제 키보드나 특수 문자를 다루거나, 기존 코드가 특정 키를 인식하지 못하는 이유를 찾을 때 특히 유용합니다. console.log를 추가하고 새로고침하는 대신 도구를 열고 키를 누르기만 하면 됩니다.

빠른 참고표

속성사용 시기Deprecated?
key문자 또는 동작 이름아니요
code물리적 키 위치아니요
keyCode없음 — 레거시 전용
which없음 — 레거시 전용
charCode없음 — 레거시 전용
ctrlKeyCtrl 눌림 확인아니요
shiftKeyShift 눌림 확인아니요
altKeyAlt/Option 눌림 확인아니요
metaKeyCmd/Win 키 눌림 확인아니요

핵심 사고방식: 먼저 e.key를 사용하세요. 키의 의미를 알려줍니다. 물리적 위치가 중요할 때만(게임 컨트롤, 레이아웃 독립 단축키) e.code를 사용하세요. 새 코드에서는 keyCode, which, charCode를 절대 사용하지 마세요.

이것만 제대로 이해하면 브라우저, 키보드 레이아웃, 운영체제에 걸쳐 일관된 키보드 인터랙션을 만들 수 있습니다. 그리고 Firefox 사용자로부터 버그 리포트를 받는 일도 사라질 겁니다.

자주 묻는 질문

이 글 공유하기

XLinkedIn

관련 글