コマンドパレットCommand palette
Ctrl+K(Macは⌘K)で画面の上に出る、検索窓つきの一覧。文字を打つと操作やページの候補が絞られ、矢印キーで選んで Enter で実行する。
表示例
Esc
- 操作
- 設定を開く Ctrl ,
- 表示の設定を変える
- ヘルプ
- 設定のヘルプを読む
アイコン: 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>
/* 貼った先のリセットに頼らないよう、この部品の中だけ 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;
}
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)