⌨️

HyperBind - キーボードショートカットをUI依存・ドメイン依存として設計したライブラリ

に公開

この記事では、キーボードショートカットがUI依存・ドメイン依存であることを前提に設計したライブラリ「HyperBind」について紹介します。
プロダクトごとに最適なUXを実現するためのツールとして、coreとUIコンポーネントを分離した設計思想を解説します。

はじめに

実装案件でよくあるキーボードショートカット。CMSの記事編集中に Ctrl+S で記事保存できるような管理画面でのショートカットはいくつもあります。
自動保存だったりもありますが、UIのパターンは利用シーンに応じて変わってきます。

様々な管理画面のUIの利用シーンで対応できる「HyperBind」を作ってみました。

https://github.com/redamoon/hyperbind-lib

https://www.npmjs.com/package/@hyperbind-lib/core

https://www.npmjs.com/package/@hyperbind-lib/react

https://www.npmjs.com/package/@hyperbind-lib/vue

モダンなWebアプリケーションにおいて、キーボードショートカットはユーザー体験を向上させる重要な要素です。しかし、その実装は意外と複雑で、プロダクトごとに異なる要件が求められます。

例えば、一般的なWebアプリケーションでは Ctrl+S でページを保存するかもしれませんが、基幹システムでは同じ Ctrl+S が「データ登録」という全く異なる操作に割り当てられることがあります。また、データ入力画面では F12 でデータを登録し、Ctrl+Insert で行を挿入するといった、業務効率を重視した独自の操作が求められます。

なお、F12キーはブラウザの開発者ツール起動キーと競合します。基幹システムではブラウザのデフォルト動作を上書きすることが許容されるケースが多いですが、その点についても考慮する必要があります。

このような多様な要件に対応するため、多くの開発者は各プロジェクトごとにカスタムのショートカットロジックを実装してきました。しかし、既存のライブラリは「一つの正解」を前提とした設計が多く、UI依存やドメイン依存の柔軟な対応が難しいという課題がありました。

さらに、Webアプリケーション特有の課題として、ブラウザという制約の中でのショートカット実装があります。ブラウザには以下のような制約があります。

  • ブラウザのデフォルト動作: Ctrl+Sで保存ダイアログが開く、F5でページがリロードされるなど、ブラウザが持つデフォルトの動作を上書きする必要がある
  • ブラウザ間の違い: Chrome、Firefox、Safariなど、ブラウザごとにキーバインドの扱いが異なる場合がある
  • セキュリティ制約: 一部のキーバインドはブラウザのセキュリティポリシーにより制限される
  • プラットフォーム間の違い: MacのCmdキーとWindows/LinuxのCtrlキーなど、OSごとのキーボードの違いを吸収する必要がある

これらの制約を考慮しながら、プロダクトごとに最適なキーバインドを実装することは、開発者にとって大きな負担となります。

HyperBindは、この課題を解決するために、キーボードショートカットを「UI依存・ドメイン依存」として認め、ブラウザという制約の中でも柔軟に対応できるよう設計されたライブラリです。

キーボードショートカットはUI依存・ドメイン依存である

なぜ「一つの正解」が存在しないのか

キーボードショートカットの設計において、最も重要な認識は「一つの正解が存在しない」ということです。同じキーコンビネーションでも、プロダクトの種類やドメインによって、全く異なる操作が求められることがあります。

基幹システムの例

基幹システムでは、業務効率を重視した独自のキーバインドが数多く定義されています。例えば、データ入力画面では以下のような操作が求められます。

キー 機能 説明
F2 新規作成 フォームをクリアし、新規入力状態にする
F8 参照 マスタデータの一覧を表示(フォーカス中の入力に応じる)
F9 削除 最後に追加した明細を削除
F12 登録 データを登録(バリデーション付き)
Ctrl+F 検索 データの検索画面を表示
Ctrl+Insert 行挿入 明細行を追加
Ctrl+Delete 行削除 選択中の明細行を削除
Ctrl+K 行切り取り 選択中の行を切り取り
Ctrl+L 行コピー 選択中の行をコピー
Ctrl+Y 行貼り付け 切り取り/コピーした行を貼り付け
Enter 次の入力へ 入力フィールド間を移動

この例では、Enterキーの動作が文脈によって異なります。

  • コード入力後:マスタデータを参照し、次のフィールドへ移動
  • 数量入力後:明細一覧に追加
  • その他の入力フィールド:次の入力へ移動

このように、同じキーでも現在のフォーカス位置や入力状態によって動作が変わるという、UI依存の設計が求められます。

一覧画面での操作例

一覧画面では、さらに異なる操作が求められます。

キー 機能 説明
F3 検索 検索条件を設定
F4 絞り込み 表示条件を絞り込む
F6 前のレコードへ 前のレコードに移動
F7 次のレコードへ 次のレコードに移動
F8 編集 選択中のレコードを編集
F9 削除 選択中のレコードを削除
F11 印刷 帳票を印刷
Ctrl+P 印刷 帳票を印刷
Alt+← / Alt+→ タブ切り替え 開いているタブを切り替え

これらの操作は、一般的なWebアプリケーションでは見られない、基幹システム特有の業務効率を重視した設計です。

一般的なWebアプリとの違い

一般的なWebアプリケーションでは、以下のような操作が一般的です。

  • Ctrl+S: 保存
  • Ctrl+Z: 元に戻す
  • Ctrl+Y: やり直し
  • Ctrl+F: 検索
  • Ctrl+P: 印刷

しかし、基幹システムや業務アプリケーションでは、これらのキーが全く異なる操作に割り当てられることがあります。例えば、基幹システムでは Ctrl+Y が「行貼り付け」に割り当てられ、Ctrl+F が「前行項目複写」に割り当てられることがあります。

プロダクトごとに最適なUXが異なる

このように、キーボードショートカットは以下の要因によって大きく変わります。

  1. ドメイン依存: 業務領域によって求められる操作が異なる
  2. UI依存: 画面の構造やフォーカス状態によって動作が変わる
  3. ユーザー依存: ターゲットユーザーの操作習慣に合わせる必要がある

したがって、「一つの正解」を前提としたライブラリでは、柔軟な対応が難しいのです。

入力要素の操作感の違いとキーバインド設計

基幹システムでは、様々な入力要素(input、textarea、selectなど)が混在するフォームが多く使われます。これらの要素は、それぞれ異なる操作感を持っており、キーバインドの設計において重要な考慮事項となります。

入力要素ごとの操作感の違い

要素 Enterキーのデフォルト動作 Tabキーのデフォルト動作 キーバインド設計の考慮点
input[type="text"] フォーム送信(submit) 次のフィールドへ移動 Enterで移動する場合、preventDefaultが必要
input[type="number"] フォーム送信 次のフィールドへ移動 数値入力中のEnterは移動に使うことが多い
textarea 改行 次のフィールドへ移動 Enterは改行として扱うべきか、移動として扱うべきか
select 選択確定 次のフィールドへ移動 Enterで選択確定後、自動的に次のフィールドへ移動するか

Enterキーによる移動設計の重要性

基幹システムでは、Enterキーでフィールド間を移動することが一般的です。しかし、textareaに到達した場合、以下のような設計判断が必要になります。

パターン1: textareaではEnterを改行として扱う

  • inputからtextareaへ:Enterで移動
  • textarea内:Enterで改行
  • textareaから次のフィールドへ:Ctrl+EnterまたはShift+Enterで移動

パターン2: textareaでもEnterで移動する

  • すべてのフィールドでEnterで移動
  • 改行はShift+Enterで行う

パターン3: 文脈に応じて動作を変える

  • 単一行のtextarea:Enterで移動
  • 複数行のtextarea:Enterで改行、Ctrl+Enterで移動

このように、同じキーでも入力要素の種類によって動作を変える必要があるため、キーバインドの移動設計が重要になります。

実装上の考慮事項

HyperBindでは、以下のような機能を提供することで、このような柔軟な設計を実現できます。

  • 要素タイプの判定: 現在フォーカスされている要素の種類を判定
  • 条件付きキーバインド: 要素の種類に応じて異なる動作を定義
  • preventDefaultの制御: ブラウザのデフォルト動作を適切に制御

Webアクセシビリティとキーバインド管理

キーボードショートカットを実装する際、Webアクセシビリティの観点から考慮すべき重要な事項があります。

モーダルやドロワー起動中のキーバインド管理

モーダルやドロワーが開いている間は、以下のような考慮が必要です。

  1. 背景のキーバインドを無効化: モーダルが開いている間は、背景のキーバインドを無効化し、モーダル内の操作に集中できるようにする
  2. フォーカストラップ: モーダル内でフォーカスが外に出ないようにする
  3. ESCキーで閉じる: 一般的なUXパターンとして、ESCキーでモーダルを閉じる
  4. モーダル専用のキーバインド: モーダル内でのみ有効なキーバインドを定義

HyperBindでは、useDisableKeyBindsWhileMountedフックを使用することで、モーダルやドロワーが開いている間、背景のキーバインドを自動的に無効化できます。

スクリーンリーダーとの互換性

スクリーンリーダーを使用しているユーザーにとって、キーボードショートカットは重要な操作手段です。以下の点に注意が必要です。

  1. 標準的なキーバインドとの競合回避: スクリーンリーダーが使用するキーバインド(例:JAWSのInsert+F、NVDAのInsert+Nなど)と競合しないようにする
  2. キーバインドの説明: キーボードショートカットの一覧を提供し、ユーザーが確認できるようにする
  3. カスタマイズ可能にする: ユーザーがキーバインドをカスタマイズできるようにする

OSやユーザー設定のキーバインドとの競合

ブラウザでのキーバインドは、OSレベルやユーザーが設定したキーバインドと競合する可能性があります。以下の点を考慮する必要があります。

  1. OSレベルのキーバインドとの競合: OSが提供するシステムショートカット(例:WindowsのWin+L(ロック)、Win+D(デスクトップ表示)、MacのCmd+Space(Spotlight検索)など)と競合しないようにする
  2. 他のアプリケーションのキーバインドとの競合: 常駐アプリケーションや他のアプリケーションで設定されているショートカットと競合する可能性がある。Macでは、ランチャーアプリのCmd+Space、テキストエディタのCmd+K、ターミナルのCmd+Tなど。Windowsでは、常駐型のランチャーアプリ、クリップボードマネージャー、スクリーンショットツールなどが使用するキーバインドと競合する可能性がある。特に、ユーザーがよく使うアプリケーションのショートカットと競合すると、操作感が悪くなる
  3. ユーザー設定のマウス・キーボードマクロ: ユーザーが設定したマウスやキーボードのマクロ、カスタムショートカットと競合する可能性がある
  4. OSのアクセシビリティ機能: OSのアクセシビリティ機能(例:Windowsのナレーター、MacのVoiceOverなど)が使用するキーバインドとの競合
  5. ブラウザ拡張機能: ブラウザ拡張機能が設定したキーバインドとの競合(例:パスワードマネージャー、翻訳ツールなど)
  6. カスタマイズ可能な設計: ユーザーがキーバインドを変更できるようにすることで、競合を回避できるようにする

HyperBindでは、予約キーの管理機能により、一般的にブラウザやOSで使用されるキーバインドを警告表示し、開発者が競合を回避できるようになることを目指しています。また、ユーザーがキーバインドをカスタマイズできる機能により、個々の環境に応じた調整が可能になる予定です。

キーボードナビゲーションの一貫性

基幹システムでは、マウスを使わずにキーボードだけで操作できることが重要です。以下の点を考慮する必要があります。

  1. Tabキーによる移動: すべての操作可能な要素にTabキーでアクセスできるようにする
  2. Enterキーによる実行: ボタンやリンクをEnterキーで実行できるようにする
  3. 矢印キーによる操作: リストやテーブルでは矢印キーで移動できるようにする
  4. 一貫した操作感: 画面全体で一貫したキーボード操作を提供する

HyperBindは、これらのアクセシビリティ要件を考慮した設計となっており、プロダクトごとに最適なキーバインド設計を実現できるようになっています。

HyperBindの設計思想:coreとUIの分離

なぜ分離したのか

HyperBindは、この多様な要件に対応するため、coreとUIコンポーネントを分離した設計を採用しています。

設計の意図

  1. フレームワーク非依存のcore: キーバインド管理のロジックをフレームワークから独立させることで、どのフレームワークでも一貫した動作を実現
  2. フレームワーク特化のUI: 各フレームワークの特性を活かしたUIコンポーネントやフックを提供
  3. プリセットの提供: よく使われるキーバインドのプリセットをcoreに含めることで、開発者の負担を軽減
  4. 拡張性: プロダクトごとに独自のキーバインドを追加・カスタマイズできる柔軟性

この分離設計により、開発者は以下のような選択が可能になります。

  • VanillaJSプロジェクト: @hyperbind-lib/core のみを使用
  • Reactプロジェクト: @hyperbind-lib/core + @hyperbind-lib/react を使用
  • Vueプロジェクト: @hyperbind-lib/core + @hyperbind-lib/vue を使用

HyperBindのアーキテクチャ

HyperBindは、以下のような階層構造で設計されています。

データフロー

キーイベントがどのように処理されるかを、以下の図で示します。

パッケージ構成と依存関係

各パッケージの関係性を以下の図で示します。

@hyperbind-lib/core: プリセットとキーバインド管理のコア

@hyperbind-lib/coreは、フレームワークに依存しないコアライブラリです。以下の機能を提供します。

  • キーバインド管理: キーコンビネーションの登録・削除・更新
  • プリセットキーバインド: よく使われるキーバインドの定義
  • 予約キーの管理: ブラウザや一般的なアプリケーションで使用されるキーの警告
  • クロスプラットフォーム対応: MacのCmdキーとWindows/LinuxのCtrlキーを統一的に扱う

coreは、UIに依存しない純粋なロジックとして設計されているため、どのフレームワークでも同じように動作します。

@hyperbind-lib/react: React用のUIコンポーネントとフック

@hyperbind-lib/reactは、ReactアプリケーションでHyperBindを簡単に使えるようにするためのラッパーです。以下の機能を提供します。

  • React Hooks: useKeybind, useCustomKeybinds, useModalKeybind など
  • UIコンポーネント: KeyRecorder, KeybindList, FormNavigator など
  • 状態管理: Reactの状態管理と統合されたキーバインド管理

Reactの特性を活かしたAPI設計により、既存のReactアプリケーションに自然に統合できます。

@hyperbind-lib/vue: Vue用のUIコンポーネントとフック

@hyperbind-lib/vueは、Vue 3アプリケーションでHyperBindを簡単に使えるようにするためのラッパーです。以下の機能を提供します。

  • Composition API: Vue 3のComposition APIに対応したフック
  • UIコンポーネント: Vue用のキーバインド管理コンポーネント
  • リアクティブな状態管理: Vueのリアクティブシステムと統合

Vueの特性を活かしたAPI設計により、既存のVueアプリケーションに自然に統合できます。

フレームワークに依存しないcoreの価値

coreとUIを分離することで、以下のような価値を提供できます。

  1. 再利用性: 同じcoreロジックを複数のフレームワークで再利用可能
  2. 保守性: coreのロジックを一箇所で管理できるため、バグ修正や機能追加が容易
  3. テスト容易性: UIに依存しない純粋なロジックのため、単体テストが書きやすい
  4. 拡張性: 新しいフレームワークに対応する際も、coreを再利用できる

この設計により、HyperBindは「一つの正解」を押し付けるのではなく、プロダクトごとに最適なUXを実現するためのツールとして機能します。

HyperBindの使い方

インストール

HyperBindは、npmパッケージとして公開されています。プロジェクトに応じて必要なパッケージをインストールしてください。

Reactプロジェクト

npm install @hyperbind-lib/core @hyperbind-lib/react

Vueプロジェクト

npm install @hyperbind-lib/core @hyperbind-lib/vue

VanillaJSプロジェクト

npm install @hyperbind-lib/core

基本的な使用例

最小限のコードで、Ctrl+S(MacではCmd+S)で保存処理を実行する例です。

Reactの場合

import { useKeybind } from '@hyperbind-lib/react';

function SaveButton() {
  const handleSave = () => {
    console.log('保存しました');
    // 保存処理を実装
  };

  useKeybind('ctrl+s', handleSave);

  return <button onClick={handleSave}>保存</button>;
}

Vueの場合

<template>
  <button @click="handleSave">保存</button>
</template>

<script setup>
import { useKeybind } from '@hyperbind-lib/vue';

const handleSave = () => {
  console.log('保存しました');
  // 保存処理を実装
};

useKeybind('ctrl+s', handleSave);
</script>

VanillaJSの場合

import { KeybindManager } from '@hyperbind-lib/core';

const manager = new KeybindManager();

manager.register('ctrl+s', () => {
  console.log('保存しました');
  // 保存処理を実装
});

これだけで、Ctrl+S(MacではCmd+S)で保存処理が実行されます。

主要な機能

HyperBindは、以下のような機能を提供します。

React/Vue用のフックとコンポーネント

  • useKeybind: シンプルなキーバインド登録
  • useCustomKeybinds: 複数のキーバインドを動的に管理(個別のオン/オフ切り替え、preventDefaultの制御、localStorageへの自動保存)
  • useModalKeybind: モーダル開閉用のキーバインド
  • useDisableKeyBindsWhileMounted: 一時的な無効化(モーダルやドロワー起動中に背景のキーバインドを無効化)
  • useGlobalKeybindToggle: グローバルなON/OFF切り替え
  • KeyRecorder: キー入力記録コンポーネント
  • KeybindList: キーバインドのリスト表示と管理コンポーネント
  • FormNavigator: フォームフィールド間のナビゲーション(Enter/Tabキー、入力要素の種類に応じた動作制御)

入力要素の操作感に対応した設計

  • 要素タイプの判定: input、textarea、selectなど、入力要素の種類に応じたキーバインド制御
  • 条件付きキーバインド: 現在のフォーカス位置や入力要素の種類に応じて異なる動作を定義
  • preventDefaultの制御: ブラウザのデフォルト動作を適切に制御し、基幹システムに適した操作感を実現

クロスプラットフォーム対応

MacのCmdキー(⌘)とWindows/LinuxのCtrlキーを統一的に扱えます。Ctrl+SまたはCmd+Sのどちらで登録しても、両方のプラットフォームで動作します。

予約キーの管理

ブラウザや一般的なアプリケーションで使用されるキーバインド(Ctrl+S、F5、Ctrl+Rなど)を予約キーとして定義し、警告を表示します。

詳細なAPIリファレンスや使用例は、GitHubリポジトリやデモアプリケーションを参照してください。

実装例とデモ

HyperBindは、様々なプロダクトで活用できます。以下のような実装例がデモアプリケーションで確認できます。

デモアプリケーションで確認できる実装例

  • データ入力画面: F2(新規作成)、F8(参照)、F9(削除)、F12(登録)などのキーバインド
  • フォーム入力: FormNavigatorを使用したフィールド間のナビゲーション
  • カスタムキーバインド管理: ユーザーが独自のキーバインドを追加・編集・削除できる管理画面

デモアプリケーション

実際の動作を確認できるデモアプリケーション。

https://redamoon.github.io/hyperbind-lib/

  • Reactデモ
  • Vueデモ
  • VanillaJSデモ: (基本のフォーム操作)

各デモのソースコードは、GitHubリポジトリのexamplesディレクトリで確認できます。

まとめ

HyperBindは、キーボードショートカットを「UI依存・ドメイン依存」として認め、プロダクトごとに最適なUXを実現するためのツールとして設計されました。

キーボードショートカットの多様性を認める設計

  • ドメイン依存: 業務領域によって求められる操作が異なる
  • UI依存: 画面の構造やフォーカス状態によって動作が変わる
  • ユーザー依存: ターゲットユーザーの操作習慣に合わせる必要がある

この多様性を認めることで、「一つの正解」を押し付けるのではなく、プロダクトごとに最適なUXを実現できるようになります。

coreとUIの分離による柔軟性

  • フレームワーク非依存のcore: どのフレームワークでも一貫した動作を実現
  • フレームワーク特化のUI: 各フレームワークの特性を活かしたAPI設計
  • 拡張性: プロダクトごとに独自のキーバインドを追加・カスタマイズ可能

この分離設計により、開発者はプロジェクトの要件に応じて柔軟にショートカット機能を組み込むことができます。

現在の状況と今後の開発

HyperBindは、現在も開発を続けており、まだ不完全な部分が多くあります。予約キーの管理機能やユーザーカスタマイズ機能など、いくつかの機能は開発中です。継続的に改善と修正を繰り返していく予定です。

参考リンク

Discussion