典型的なショートカットの不具合:QWERTY の Mac ではコマンドパレットが Cmd+K で開き、/ で検索にフォーカスするのに、フランス語 AZERTY 配列の同僚からは / に Shift が要ってショートカットが一切効かないと報告が来る。ハンドラーで e.key, e.code, e.keyCode を出力すると、値は配列、修飾キー、デッドキーによって変わる。

JavaScript Keycode Explorer を開く →

フロントエンドエンジニアなら誰もが一度ははまる沼だ。手元のキーボードハンドラがオフィスを離れた瞬間に壊れる。解決策は「別のプロパティを試す」ことではない。key・code・非推奨の keyCode という3つのプロパティが実際に何を意味しているか、そして自分が問いたい質問にどれが対応しているかを理解することだ。

どのプロパティを選ぶかは、どんな質問をしているかと同義

ブラウザが1回のキー押下に対して3つの異なるビューを提供するのは、キーボード入力に3つの異なるレイヤーがあるからだ。

プロパティ追跡対象不変条件QWERTY で A キーを押した例
event.keyレイアウトと修飾キー適用後の文字または名前付きアクションユーザーがこの文字の出力を期待している"a"、Shift 併用で "A"
event.code標準 US QWERTY 配列における物理キー位置ユーザーが同じ物理位置に指を動かす"KeyA"
event.keyCodeレガシーな数値コード。実際にはそのキーの Windows 仮想キーコード2003 年に戻りたいとき65

混乱は一文で整理できる。key は「ユーザーが生成した文字は何か?」、code は「ユーザーの指はキーボード上のどこにあるか?」、keyCode はモダンブラウザを跨いでどちらにも確実には答えない。

Keycode Explorer は3つすべてを並べて表示する。キャプチャパッドで任意のキーを押すと、event.key、event.code、旧式の keyCode / which / charCode、修飾キーの状態、repeat フラグ、isComposing が見える。さらに、押下したキーと修飾キーの組み合わせに合った正しいプロパティを使う、コピペ可能な keydown ハンドラまで組み立ててくれる。

key を使うべきとき

物理位置ではなく、文字または名前付きアクションにユーザーの意図が紐づいているときは event.key を使う。典型例は以下の通り。

  • 文字に対応するエディタショートカット。Ctrl+/ でコメントトグル。ユーザーは自分のレイアウトがどう / を生成するかに関わらず、/ を生み出した瞬間にコメントトグルが発火することを期待している。AZERTY では / は : と同じキーにあり Shift が必要だが、押下時の event.key は依然として "/" だ。
  • フォームヘルパー。「n で次へ、p で前へ」。物理位置ではなく意味的な文字を使う。
  • 名前付きキー。event.key === "Escape"、event.key === "ArrowDown"、event.key === "Enter" は UI Events KeyboardEvent key Values 仕様で定められた安定した識別子だ。これを使え。

event.key はリテラル文字、"ArrowLeft"、"Tab"、"F5" のような名前付きキー、あるいは——意外なことに——合成入力シーケンスの前半(US Mac で Option+E、アクセント付き母音を待ち受けている状態)では "Dead" を返す。Keycode Explorer はリアルタイムで Dead を表示するので、ハンドラがそれらのキーストロークを文字入力として扱わず無視すべきだと、最も簡単に気づける。

code を使うべきとき

event.code は US QWERTY 配列を前提とした物理キー位置だ。ユーザーの筋肉記憶が文字ではなくキーボード上の位置に紐づいているとき、これが正解になる。代表的な2ケース。

  • WASD のゲーム操作。 AZERTY では WASD の位置にあるキーの刻印が Z、Q、S、D になる。event.key === "w" で判定すると、フランスのプレイヤーは最下段の W に手を伸ばす羽目になる。event.code === "KeyW" で判定すれば物理的な位置は変わらない。
  • 修飾キー無視のショートカット。event.code === "Slash" は、文字を出すのに Shift が必要かどうかに関わらず物理的な / キーにマッチする。e.shiftKey === false と組み合わせれば厳密なバインディングになる。

event.code の値は UI Events code Values 仕様に由来する。KeyA、Digit1、Numpad9、ArrowDown、IntlBackslash、Lang1 のような見た目だ。リストを暗記する価値はない。Keycode Explorer はキーを押した瞬間に値を表示する。

keyCode・which・charCode は何をしようとしていたのか

IE 4 / Netscape 4 の時代、ブラウザは PC スキャンコードと Windows 仮想キーテーブルに遡る整数の「キーコード」を提供していた。レガシーコードベースに必ず存在する古典的なページにはこう書かれている。

if (e.keyCode === 13) submitForm();        // Enter
if (e.keyCode === 27) closeDialog();       // Escape
if (e.keyCode === 191) toggleComment();    // The "/" key

このコードは、ショートカットごとに気にすべきキーボード配列が1つ、OS ファミリーが1つ、ブラウザが1つだった時代に動いていた。今や無数の本番アプリケーションでこのコードが屋台骨を支えている。だからこそ W3C UI Events 仕様が レガシーとマーク しているにも関わらず、ブラウザは今でも keyCode を提供し続けている。

keyCode 属性はシステム依存・実装依存の数値コードを表す……現代のスクリプトの作者には、代わりに key および code 属性を使うことを推奨する。

同じ段落は charCode と which にもさらに厳しい扱いをしている。両者とも「実装は後方互換性のためにサポートすべき」代替手段として明示的に定義されつつ、特に非ラテン配列ではブラウザ間で値に一貫性がないと注意喚起されている。

Keycode Explorer はモダンな値の隣に旧3値すべてを表示する。これが2026年でも重要な理由は2つ。

  1. ショートカット層全体が if (e.keyCode === N) で書かれたアプリを引き継いで移行する必要がある。同じ押下に対するモダンな event.key を相互参照することで、書き換え対象が分かる。
  2. 自分で変更できない外部ライブラリが今も keyCode をキーにしたキーボードトラッキングイベントを発行している。Explorer がどの数値が期待されるかを示してくれるので、互換シムを書ける。

修飾キー:ショートカットの片割れ

キーボードショートカットはほとんどの場合「このキー」ではなく「このキー+これらの修飾キーを押しながら」だ。Explorer は4つのピル(Shift、Ctrl、Alt、Meta)を描画し、押すたびに点灯する。内部の KeyboardEvent ではブール値として公開されている。

e.shiftKey   // Shift on either side
e.ctrlKey    // Control on either side
e.altKey     // Alt / Option
e.metaKey    // Command on macOS, Windows on Windows, Super on Linux

実装すると初回に必ず引っかかる3点。

  1. e.metaKey はプラットフォーム依存。macOS のユーザーは Cmd を期待し、Windows のユーザーは Ctrl を期待する。古典的なパターンは e.metaKey || e.ctrlKey。二重発火を避けるためにプラットフォーム検出で優位な方を選び、もう片方は no-op にする実装も多い。
  2. 修飾キー単独の押下でも keydown は発火する。Shift だけを押すと event.key === "Shift"、event.code === "ShiftLeft" または "ShiftRight" が来る。「修飾キー以外なら何でも」というハンドラは、e.key が {"Shift", "Control", "Alt", "Meta"} のときに早期 return する必要がある。
  3. CapsLock と NumLock は同じ意味での修飾キーではなく、状態だ。読み取るには e.getModifierState("CapsLock") を使う。Explorer は CapsLock オンの状態でキーを押すと完全性のためにこの情報を表示する——key の値は大文字小文字が反転するが、code は変わらない。

Explorer がキーストロークごとに生成するスニペットは、4つの修飾フラグをライブで読み取り、最小限の正しい条件式を出力する。Cmd+Shift+P を押すと、スニペットは次のようになる。

document.addEventListener('keydown', (e) => {
  if (e.key === 'p' && e.ctrlKey === false && e.shiftKey && e.metaKey) {
    // your handler
  }
});

コピーして貼り付け、バインドに関係ない条件を消せば終わりだ。このパターンに特別なところは何もない。

IME 変換は逃げ切れないバグではない

macOS の Chrome で日本語または中国語の IME を有効にして Explorer を開く。「kanji」と打つと、event.isComposing === true かつ event.key === "Process" の keydown イベントが連鎖して発生する。IME は変換候補ウィンドウへキーストロークを集めている最中で、ブラウザは「これらをまだ文字として扱うな」と伝えている。Explorer はプロパティグリッドで isComposing を直接表示するので、切り替わる瞬間が見える。

ルールは短い:event.isComposing === true なら、単キーのハンドラーはすぐ return すること。 さもないと「日本語」を変換中に n を押したとき「次へ」のショートカットが発火し、IME への打鍵を奪ってしまう。チームに IME で入力する人がいないと見落としやすい不具合だ。

Explorer が生成するスニペットには現在のところ isComposing ガードは自動注入されない——その判断はハンドラの意味論に属する。だが Process と isComposing: true がプロパティグリッドに見えれば、必要なガードは自明だ。

document.addEventListener('keydown', (e) => {
  if (e.isComposing) return;
  // ... rest of your shortcut logic
});

「なぜスマホで動かないんですか?」

Explorer はこの点を明示している。モバイルでの物理キーボードイベントは信頼できない。iOS Safari やほとんどの Android ブラウザのソフトキーボードは、文字入力に対して keydown を一貫して発火しない。代わりに、ユーザーはフォーカスされたフィールドで最終文字列を含む input イベントを受け取る。Keycode Explorer は粗いポインタを検出すると、input イベントを読み取って key 値を推測する単一文字 <input> を部分的なフォールバックとして表示するが、旧式の keyCode / which フィールドを正直に埋めることはできない。

ソフトウェアキーボードがそもそも生成していない配列やスキャンコードの情報は、Web プラットフォームでは取り戻せない。このフォールバックで Explorer は入力した文字を key に表示するが、code 欄はその文字から推測した値にすぎない(a なら KeyA、それ以外は —)。本物の code 値として使わないこと。キーボードイベントを完全に調べたいならデスクトップのブラウザーを使い、モバイルのキーボード処理は、タッチ端末で実際に頼れる input と beforeinput イベントを中心に設計してほしい。

実践レシピ

本番のキーボード処理コードはほぼこの4パターンでカバーできる。

// 1. Toggle a command palette with Cmd+K (Mac) or Ctrl+K (everywhere else).
document.addEventListener('keydown', (e) => {
  if (e.isComposing) return;
  const cmdOrCtrl = e.metaKey || e.ctrlKey;
  if (cmdOrCtrl && e.key === 'k') {
    e.preventDefault();
    togglePalette();
  }
});
// 2. WASD movement that survives AZERTY (uses code, not key).
const moves = { KeyW: 'up', KeyA: 'left', KeyS: 'down', KeyD: 'right' };
document.addEventListener('keydown', (e) => {
  const dir = moves[e.code];
  if (dir && !e.repeat) move(dir);
});
// 3. Slash to focus the global search, including when produced via Shift.
document.addEventListener('keydown', (e) => {
  if (e.isComposing) return;
  if (e.key === '/' && document.activeElement === document.body) {
    e.preventDefault();
    document.querySelector('#search-input').focus();
  }
});
// 4. Escape that doesn't fight the browser when nothing is open.
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && isAnyModalOpen()) {
    e.preventDefault();
    closeTopModal();
  }
});

パターンは常に同じだ。文字または名前付きアクションが欲しければ key、物理位置が欲しければ code を選ぶ。isComposing でゲートする。実際にイベントを消費したときだけ preventDefault() を呼ぶ。

keypress について一言

古いコードにはまだ keypress が残っているが、新しく使わないこと。UI Events 仕様は keypress を非推奨としており、実装も一貫しない。文字を生むキーでしか発火せず、IME 入力では確実に発火せず、渡される charCode もやはり非推奨だ。Explorer は keydown だけを監視する。テキスト入力にはフォーカス中の要素の input / beforeinput、ショートカットには keydown を使う。

Recent Keys:ちょっとした UX の工夫

Explorer はスニペットの下に、直近8件のキーラベルをチップとして保持する。これは履歴のためではない——パターン発見のためだ。複数キーのショートカットをデバッグするとき、チップ列を見れば「Tab は本当に Enter の前に発火したか、それともブラウザに食われたか?」をプロパティグリッドをスクロールすることなく確認できる。同じ仕掛けは自作のキーボードハンドラでも有効だ。直近 N 件の event.key 値をリングバッファに集め、描画する。console.log が陳腐化しても生き残る無料のデバッグツールができあがる。

ZeroTool と他の Keycode ツールの比較

Toptal の keycode.info は定番のリファレンスで今も動く。Explorer の違いは意図的なものだ。

  • すべてのプロパティを一括表示。keycode.info はモダンな値を中心に表示する。Explorer は key、code、keyCode、which、charCode、location、repeat、isComposing を1つのグリッドに出力するので、1回の押下を一目で監査できる。
  • 修飾キーのライブ描画。文字キーを押さずに Shift+Alt を押し続けてみる——4つのピルが keydown ごとに更新され、実際のキーストロークをコミットする前に修飾状態を正確に把握できる。
  • 直前の押下に合わせたスニペット。 Explorer は直前に押したキーと修飾キーの組み合わせに合わせた keydown リスナーを出力し、Ctrl を押していない状態を条件にしたいときは e.ctrlKey === false も明示する。
  • IME と isComposing の可視化。多くのリファレンスツールは isComposing を表示しない。Explorer は表示する。このフラグは IME 関連のショートカットバグの最大の発生源だからだ。
  • ブラウザ内で処理、多言語 SEO コピー。Explorer は4つの URL に存在する——/tools/keycode-explorer/、/zh/tools/keycode-explorer/、/ja/tools/keycode-explorer/、/ko/tools/keycode-explorer/——同じ JavaScript エンジンとローカライズされた UI コピーを備える。

さらに読む

自分の環境では動くショートカットが他人の環境で壊れたら、次は Keycode Explorer を使う。相手が押したキーを押し、ブラウザが実際に何を発火するかを見て、合致するスニペットをコピーし、先へ進む。