No.142
MODAL CSSJS

Command Palette

⌘K / Ctrl+K で開き、入力で候補を絞り込み、矢印キーで選んで Enter で実行するコマンドパレット。開閉のアニメーションとキーボード操作、フォーカスの戻し方までまとめています。

DEMO

CODE

HTML
<div class="demo-stage cmd-stage" id="cmd-stage">
  <button type="button" class="cmd-open" id="cmd-open"><span>コマンドを検索</span><kbd>Ctrl</kbd><kbd>K</kbd></button>
  <div class="cmd-backdrop" id="cmd-backdrop" hidden>
    <div class="cmd-panel" role="dialog" aria-modal="true" aria-label="コマンドパレット">
      <input class="cmd-input" id="cmd-input" type="text" placeholder="コマンドを入力…" autocomplete="off" role="combobox" aria-expanded="true" aria-controls="cmd-list">
      <ul class="cmd-list" id="cmd-list" role="listbox"></ul>
      <p class="cmd-result" id="cmd-result" role="status" aria-live="polite"></p>
    </div>
  </div>
</div>
CSS
.cmd-stage { position:relative; display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:320px; overflow:hidden; }
.cmd-open { display:flex; align-items:center; gap:.4rem; padding:.7rem 1rem; border-radius:10px; border:1px solid rgba(255,255,255,.14); background:#15151B; color:#C9C9D1; font-size:.9rem; cursor:pointer; }
.cmd-open span { margin-right:.6rem; }
.cmd-open:focus-visible, .cmd-item:focus-visible { outline:2px solid #E91B89; outline-offset:2px; }
.cmd-open kbd { font-family:'JetBrains Mono',monospace; font-size:.7rem; padding:.1rem .35rem; border-radius:4px; background:#26262F; border:1px solid rgba(255,255,255,.12); }
.cmd-backdrop { position:absolute; inset:0; display:flex; align-items:flex-start; justify-content:center; padding-top:2.2rem; background:rgba(5,5,8,.6); backdrop-filter:blur(3px); animation: cmd-fade .18s ease both; }
.cmd-backdrop[hidden] { display:none; }
.cmd-panel { width:min(92%, 380px); background:#16161C; border:1px solid rgba(255,255,255,.12); border-radius:14px; box-shadow:0 24px 60px -12px rgba(0,0,0,.7); overflow:hidden; animation: cmd-pop .22s cubic-bezier(.22,1,.36,1) both; }
.cmd-input { width:100%; box-sizing:border-box; padding:.95rem 1.1rem; font-size:16px; color:#EDEDED; background:transparent; border:0; border-bottom:1px solid rgba(255,255,255,.08); outline:none; }
.cmd-list { list-style:none; margin:0; padding:.4rem; max-height:180px; overflow:auto; }
.cmd-item { display:flex; justify-content:space-between; padding:.6rem .75rem; border-radius:8px; color:#C9C9D1; font-size:.86rem; cursor:pointer; }
.cmd-item small { color:#6E6E7A; font-family:'JetBrains Mono',monospace; }
.cmd-item[aria-selected="true"] { background:rgba(233,27,137,.16); color:#fff; }
.cmd-empty { padding:.8rem; color:#6E6E7A; font-size:.84rem; }
.cmd-result { margin:0; padding:.5rem 1.1rem .8rem; color:#34D399; font-size:.78rem; min-height:1em; }
@keyframes cmd-fade { from { opacity:0; } to { opacity:1; } }
@keyframes cmd-pop { from { opacity:0; transform:translateY(-8px) scale(.97); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce) {
  .cmd-backdrop, .cmd-panel { animation-duration:.01ms; }
}
JS
(function(){
  var stage = document.getElementById('cmd-stage');
  var openBtn = document.getElementById('cmd-open');
  var bd = document.getElementById('cmd-backdrop');
  var input = document.getElementById('cmd-input');
  var list = document.getElementById('cmd-list');
  var result = document.getElementById('cmd-result');
  if (!stage || !openBtn || !bd || !input || !list) return;
  var CMDS = [['新規ファイルを作成','N'],['ファイルを開く','O'],['保存','S'],['テーマを切り替え','T'],['設定を開く',','],['ヘルプを表示','?']];
  var shown = [], sel = 0;
  function render(){
    var q = input.value.trim().toLowerCase();
    shown = CMDS.filter(function(c){ return c[0].toLowerCase().indexOf(q) !== -1; });
    sel = Math.min(sel, Math.max(shown.length - 1, 0));
    list.innerHTML = shown.length ? '' : '<li class="cmd-empty">該当するコマンドはありません</li>';
    shown.forEach(function(c, i){
      var li = document.createElement('li');
      li.className = 'cmd-item'; li.id = 'cmd-opt-' + i; li.setAttribute('role', 'option');
      li.setAttribute('aria-selected', i === sel ? 'true' : 'false');
      li.innerHTML = '<span>' + c[0] + '</span><small>' + c[1] + '</small>';
      li.addEventListener('mousemove', function(){ if (sel !== i) { sel = i; mark(); } });
      li.addEventListener('click', function(){ sel = i; run(); });
      list.appendChild(li);
    });
    input.setAttribute('aria-activedescendant', shown.length ? 'cmd-opt-' + sel : '');
  }
  function mark(){
    Array.prototype.forEach.call(list.children, function(li, i){ li.setAttribute('aria-selected', i === sel ? 'true' : 'false'); });
    input.setAttribute('aria-activedescendant', shown.length ? 'cmd-opt-' + sel : '');
    var cur = list.children[sel]; if (cur && cur.scrollIntoView) cur.scrollIntoView({ block: 'nearest' });
  }
  function open(){ bd.hidden = false; input.value = ''; sel = 0; result.textContent = ''; render(); input.focus(); }
  function close(){ bd.hidden = true; openBtn.focus(); }
  function run(){ if (!shown[sel]) return; result.textContent = '実行: ' + shown[sel][0]; setTimeout(close, 700); }
  openBtn.addEventListener('click', open);
  bd.addEventListener('click', function(e){ if (e.target === bd) close(); });
  input.addEventListener('input', function(){ sel = 0; render(); });
  input.addEventListener('keydown', function(e){
    if (e.key === 'ArrowDown') { e.preventDefault(); if (shown.length) { sel = (sel + 1) % shown.length; mark(); } }
    else if (e.key === 'ArrowUp') { e.preventDefault(); if (shown.length) { sel = (sel - 1 + shown.length) % shown.length; mark(); } }
    else if (e.key === 'Enter') { e.preventDefault(); run(); }
    else if (e.key === 'Escape') { e.preventDefault(); close(); }
    else if (e.key === 'Tab') { e.preventDefault(); }
  });
  document.addEventListener('keydown', function(e){
    if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) { e.preventDefault(); if (bd.hidden) open(); else close(); }
  });
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
⌘K(Windows は Ctrl+K)で開くコマンドパレットを作って。背景を半透明+backdrop-filter: blur で暗くし、パネルは translateY(-8px) scale(.97) から .22秒で現れる。上部の入力欄に文字を打つとコマンド一覧を部分一致で絞り込み、該当ゼロなら「該当するコマンドはありません」を表示する。↑↓キーで選択行を移動(端で反対側へ回り込む)、Enter で実行、Esc と背景クリックで閉じる。選択行は aria-selected="true" で示し、入力欄に role="combobox" と aria-activedescendant を付けてスクリーンリーダーにも現在の候補を伝えること。開いたら入力欄にフォーカスし、開いている間は Tab でパネルの外へ出ないようにし(aria-modal の約束)、閉じたら開いたボタンへフォーカスを戻す。マウスを乗せた行も選択状態に同期させる。キーボードショートカットは document の keydown で metaKey || ctrlKey を見て preventDefault する。@media (prefers-reduced-motion: reduce) でアニメーションを .01ms にする。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 UIデザインの心理学 ―わかりやすさ・使いやすさの法則 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

RELATED EFFECTS

↗ MORE
→ POPOVER API MENU → MODAL SCALE BLUR → EXPANDING SEARCH

Command Palette(コマンドパレット)とは

キーボードショートカットで呼び出し、文字を打って機能を探して実行する検索型のメニューです。VS Code の Ctrl+Shift+P や、Slack・GitHub・Linear の ⌘K で広まり、機能が多いWebアプリで「メニューを探す」手間を減らす部品として定番になりました。

当サイトの Popover API Menu はボタンの近くに開く小さなメニュー、Modal Scale Blur は情報を見せるためのモーダルです。本ページは入力による絞り込みとキーボードだけの操作を中心にした別テーマで、マウスに手を伸ばさずに作業を続けたい利用者に向きます。

見た目よりも操作の作法が品質を決めます。矢印キーでの移動、Enter での実行、Esc で閉じる、閉じたらフォーカスを戻す、選択中の候補を読み上げに伝える、の5点がそろって初めて「使える」パレットになります。

よくある質問

Ctrl+K はブラウザのショートカットと衝突しませんか?

Chrome などでは Ctrl+K がアドレスバーでの検索に割り当てられていますが、ページ側の keydown で preventDefault() を呼べばページの操作が優先されます。VS Code・Slack・GitHub など多くのツールが同じキーを採用しているため、利用者にとって覚えやすい組み合わせです。

閉じたあとにフォーカスを戻すのはなぜですか?

パネルを閉じた時にフォーカスがどこにも無いと、キーボード利用者は現在位置を見失い、Tab で最初からたどり直すことになります。開いたボタンへ戻すと、閉じる前と同じ場所から操作を続けられます。モーダル全般に共通する作法です。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。