Webパーツ辞典

コマンドパレットCommand palette

Ctrl+K(Macは⌘K)で画面の上に出る、検索窓つきの一覧。文字を打つと操作やページの候補が絞られ、矢印キーで選んで Enter で実行する。

表示例

アイコン: Font Awesome 7 Free(fa-solid / fa-regular)

別名・英語表記

  • コマンドメニュー
  • Ctrl+Kメニュー
  • ⌘Kメニュー
  • クイックコマンド
  • コマンド検索窓
  • Command palette

実装例

<!-- 実際は dialog 要素を showModal() で開く(aria-modal と背後の inert はブラウザが持つ)。見本は流れの中に置いている -->
<div class="x-command-palette" role="dialog" aria-label="コマンドパレット">
  <div class="x-command-palette__head">
    <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
    <input class="x-command-palette__i" type="text" value="せってい" autocomplete="off"
           role="combobox" aria-label="コマンドを検索" aria-autocomplete="list" aria-expanded="true"
           aria-controls="x-command-palette-list" aria-activedescendant="x-command-palette-o1">
    <kbd class="x-command-palette__esc">Esc</kbd>
  </div>
  <div class="x-command-palette__list" id="x-command-palette-list" role="listbox" aria-label="コマンド">
    <ul class="x-command-palette__g" role="group" aria-labelledby="x-command-palette-g1">
      <li class="x-command-palette__gh" id="x-command-palette-g1" role="presentation">操作</li>
      <li class="x-command-palette__o" id="x-command-palette-o1" role="option" aria-selected="true">
        <i class="fa-solid fa-gear" aria-hidden="true"></i>
        <span class="x-command-palette__label">設定を開く</span>
        <kbd class="x-command-palette__key">Ctrl ,</kbd>
      </li>
      <li class="x-command-palette__o" id="x-command-palette-o2" role="option" aria-selected="false">
        <i class="fa-solid fa-display" aria-hidden="true"></i>
        <span class="x-command-palette__label">表示の設定を変える</span>
      </li>
    </ul>
    <ul class="x-command-palette__g" role="group" aria-labelledby="x-command-palette-g2">
      <li class="x-command-palette__gh" id="x-command-palette-g2" role="presentation">ヘルプ</li>
      <li class="x-command-palette__o" id="x-command-palette-o3" role="option" aria-selected="false">
        <i class="fa-solid fa-file-lines" aria-hidden="true"></i>
        <span class="x-command-palette__label">設定のヘルプを読む</span>
      </li>
    </ul>
  </div>
  <p class="x-command-palette__foot" aria-hidden="true">
    <span><kbd>↑</kbd><kbd>↓</kbd> 移動</span>
    <span><kbd>Enter</kbd> 実行</span>
  </p>
</div>

AIへの指示文

「コマンドパレット(Command palette)」を実装してください。

## これは何か
Ctrl+K(Macは⌘K)で画面の上に出る、検索窓つきの一覧。文字を打つと操作やページの候補が絞られ、矢印キーで選んで Enter で実行する。

## 使いどころ
- 機能やページが多く、メニューを辿るより名前で呼び出したほうが速いアプリ
- キーボード中心で使う人のために、主要な操作を1か所から呼べるようにしたいとき

## 実装の要件
- dialog を showModal() で開き、中に input[role=combobox] + div[role=listbox] > ul[role=group] > li[role=option] を置く
- Ctrl+K / ⌘K で開閉、↑↓で候補を移動、Enter で実行、Esc で閉じる
- 候補の件数の変化は aria-live="polite" の領域で控えめに伝える
- どのキーの処理も(Ctrl+K / ⌘K の開閉を含む)、keydown の event.isComposing が true か event.keyCode が 229 のとき(日本語の変換中)は何もしない
- 変換中の↑↓は変換候補の選択、Enter は確定に使われ、Mac の日本語入力では Control+K が変換中の文字をカタカナにする。Safari 26 以前は確定の Enter で isComposing が false になるので、keyCode 229 も併せて見る
- ここにしか無い操作を作らない。知らない人は開き方にも気づかない。同じ操作をメニューやボタンからも辿れるようにする
- Ctrl+K はブラウザでも検索欄へ移るキーに割り当てられている。奪うなら preventDefault し、開き方を画面のどこかに示す
- 検索ボックスはサイト内の記事を探す入口、オートコンプリートは1つの欄に値を入れる補助。コマンドパレットは操作を実行するための窓で、選ぶとその場で実行される
- 候補が0件のときに空白にしない。「一致するコマンドはありません」と出す

## アクセシビリティの要件
- 入力欄は role="combobox" に aria-expanded / aria-controls / aria-autocomplete="list" を付け、候補は role="listbox" と role="option" にする(WAI-ARIA APG の Combobox パターン)
- フォーカスは入力欄に置いたまま、↑↓で選んでいる候補を aria-activedescendant で指し、その候補に aria-selected="true" を付ける
- 区分けは role="group" にして aria-labelledby で名前を付ける
- 全体はモーダルの dialog にする。Esc で閉じ、開く前にフォーカスがあった要素へ戻す
- 開くボタンを置くなら aria-keyshortcuts="Control+K Meta+K" でショートカットを伝える。「/」のような1文字だけのショートカットにするなら、無効にするか別のキーに変えられるようにする(WCAG 2.1.4)

## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。

```html
<!-- 実際は dialog 要素を showModal() で開く(aria-modal と背後の inert はブラウザが持つ)。見本は流れの中に置いている -->
<div class="x-command-palette" role="dialog" aria-label="コマンドパレット">
  <div class="x-command-palette__head">
    <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
    <input class="x-command-palette__i" type="text" value="せってい" autocomplete="off"
           role="combobox" aria-label="コマンドを検索" aria-autocomplete="list" aria-expanded="true"
           aria-controls="x-command-palette-list" aria-activedescendant="x-command-palette-o1">
    <kbd class="x-command-palette__esc">Esc</kbd>
  </div>
  <div class="x-command-palette__list" id="x-command-palette-list" role="listbox" aria-label="コマンド">
    <ul class="x-command-palette__g" role="group" aria-labelledby="x-command-palette-g1">
      <li class="x-command-palette__gh" id="x-command-palette-g1" role="presentation">操作</li>
      <li class="x-command-palette__o" id="x-command-palette-o1" role="option" aria-selected="true">
        <i class="fa-solid fa-gear" aria-hidden="true"></i>
        <span class="x-command-palette__label">設定を開く</span>
        <kbd class="x-command-palette__key">Ctrl ,</kbd>
      </li>
      <li class="x-command-palette__o" id="x-command-palette-o2" role="option" aria-selected="false">
        <i class="fa-solid fa-display" aria-hidden="true"></i>
        <span class="x-command-palette__label">表示の設定を変える</span>
      </li>
    </ul>
    <ul class="x-command-palette__g" role="group" aria-labelledby="x-command-palette-g2">
      <li class="x-command-palette__gh" id="x-command-palette-g2" role="presentation">ヘルプ</li>
      <li class="x-command-palette__o" id="x-command-palette-o3" role="option" aria-selected="false">
        <i class="fa-solid fa-file-lines" aria-hidden="true"></i>
        <span class="x-command-palette__label">設定のヘルプを読む</span>
      </li>
    </ul>
  </div>
  <p class="x-command-palette__foot" aria-hidden="true">
    <span><kbd>↑</kbd><kbd>↓</kbd> 移動</span>
    <span><kbd>Enter</kbd> 実行</span>
  </p>
</div>
```

```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-command-palette,
.x-command-palette *:not([class*="fa-"]),
.x-command-palette *:not([class*="fa-"])::before,
.x-command-palette *:not([class*="fa-"])::after {
  box-sizing: border-box;
  /* 貼った先の既定の書体に引きずられないようにする。書体が変わると
     文字の幅と高さが変わり、枠のサイズまでずれる */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}

/* 実際は画面上部の中央に出し、背後に暗幕を敷く */
.x-command-palette {
  width: 340px;
  max-width: 100%;  /* 狭い画面では親の幅に合わせて縮む */
  border: 1px solid #cfd4dc;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 12px 28px rgba(27, 32, 40, 0.18);
  overflow: hidden;
}
.x-command-palette__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-bottom: 1px solid #e3e6ea;
  color: #5b6472;
  font-size: 14px;
}
.x-command-palette__i {
  flex: 1;
  min-width: 0;
  height: 44px;
  border: 0;
  background: none;
  color: #1b2028;
  font: inherit;
  font-size: 14px;
}
/* 枠の代わりに、欄の下の線を青く太くしてフォーカスを示す */
.x-command-palette__i:focus {
  outline: none;
}
.x-command-palette__head:focus-within {
  box-shadow: inset 0 -2px 0 #1d4ed8;
}
.x-command-palette kbd {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid #cfd4dc;
  border-radius: 4px;
  background: #f9fafb;
  color: #4b5563;
  font-size: 10.5px;
  text-align: center;
}
.x-command-palette__list {
  padding: 4px 6px 6px;
}
.x-command-palette__g {
  margin: 0;
  padding: 0;
  list-style: none;
}
.x-command-palette__gh {
  padding: 6px 8px 3px;
  color: #5b6472;
  font-size: 11px;
  font-weight: 700;
}
.x-command-palette__o {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 34px;
  padding: 0 8px;
  border-left: 3px solid transparent;
  border-radius: 6px;
  color: #1b2028;
  font-size: 13px;
  cursor: pointer;
}
.x-command-palette__o .fa-solid {
  width: 16px;
  color: #5b6472;
  font-size: 13px;
}
.x-command-palette__label {
  flex: 1;
}
/* いま Enter で実行される項目。色だけにせず、左の線と太字でも示す */
.x-command-palette__o[aria-selected="true"] {
  border-left-color: #1d4ed8;
  background: #e8efff;
  font-weight: 700;
}
.x-command-palette__o[aria-selected="true"] .fa-solid {
  color: #1d4ed8;
}
.x-command-palette__foot {
  display: flex;
  gap: 14px;
  margin: 0;
  padding: 7px 12px;
  border-top: 1px solid #e3e6ea;
  background: #f9fafb;
  color: #5b6472;
  font-size: 11px;
}
.x-command-palette__foot kbd {
  margin-right: 2px;
}
```

## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。

使いどころ

  • 機能やページが多く、メニューを辿るより名前で呼び出したほうが速いアプリ
  • キーボード中心で使う人のために、主要な操作を1か所から呼べるようにしたいとき

注意点

  • ここにしか無い操作を作らない。知らない人は開き方にも気づかない。同じ操作をメニューやボタンからも辿れるようにする
  • Ctrl+K はブラウザでも検索欄へ移るキーに割り当てられている。奪うなら preventDefault し、開き方を画面のどこかに示す
  • 検索ボックスはサイト内の記事を探す入口、オートコンプリートは1つの欄に値を入れる補助。コマンドパレットは操作を実行するための窓で、選ぶとその場で実行される
  • 候補が0件のときに空白にしない。「一致するコマンドはありません」と出す

アクセシビリティの要点

  • 入力欄は role="combobox" に aria-expanded / aria-controls / aria-autocomplete="list" を付け、候補は role="listbox" と role="option" にする(WAI-ARIA APG の Combobox パターン)
  • フォーカスは入力欄に置いたまま、↑↓で選んでいる候補を aria-activedescendant で指し、その候補に aria-selected="true" を付ける
  • 区分けは role="group" にして aria-labelledby で名前を付ける
  • 全体はモーダルの dialog にする。Esc で閉じ、開く前にフォーカスがあった要素へ戻す
  • 開くボタンを置くなら aria-keyshortcuts="Control+K Meta+K" でショートカットを伝える。「/」のような1文字だけのショートカットにするなら、無効にするか別のキーに変えられるようにする(WCAG 2.1.4)

作るときに使えるツール

見分け方

関連するパーツ