About this snippet

Press Cmd+K on a Mac or Ctrl+K elsewhere to open a centered search modal. Escape closes it, clicking the dark area around it closes it, and the same shortcut toggles it shut again.

One keydown listener on window handles all of that. It checks metaKey or ctrlKey alongside the k key and calls preventDefault, which is what stops Chrome and Firefox claiming the shortcut for their own search. Opening moves focus into the input so you can start typing straight away.

Clicking outside works by testing that the click target is the backdrop element itself rather than anything inside the modal, which avoids the usual bug where selecting a list item closes the panel. The command list is static markup and the input does not filter it yet, so wire that up to your own data before shipping it.

Size
7.1 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
283
<div class="palette-instruction">
  <h2>Command Palette</h2>
  <p>Press <kbd>Ctrl + K</kbd> or <kbd>Cmd + K</kbd> to open the menu.</p>
  <button id="open-palette-btn" class="manual-open-btn">Open Manually</button>
</div>

<div class="palette-backdrop" id="cmd-backdrop">
  <div class="palette-modal">
    <div class="palette-header">
      <svg class="search-icon" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
      <input type="text" class="palette-input" placeholder="Search commands, apps, or files...">
      <div class="esc-hint">ESC</div>
    </div>
    
    <div class="palette-body">
      <div class="palette-group-title">SUGGESTED</div>
      
      <div class="palette-item active">
        <div class="item-icon blue-icon">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
        </div>
        <span class="item-name">Go to Dashboard</span>
        <span class="item-shortcut">↵</span>
      </div>
      
      <div class="palette-item">
        <div class="item-icon purple-icon">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
        </div>
        <span class="item-name">Edit Profile</span>
      </div>
      
      <div class="palette-item">
        <div class="item-icon green-icon">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
        </div>
        <span class="item-name">System Settings</span>
      </div>
    </div>
  </div>
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  margin: 0;
  background: #f1f5f9;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.palette-instruction {
  text-align: center;
  color: #0f172a;
}

kbd {
  background: #e2e8f0;
  padding: 4px 8px;
  border-radius: 6px;
  border: 1px solid #cbd5e1;
  border-bottom-width: 2px;
  font-weight: 600;
  font-family: monospace;
  font-size: 0.9rem;
}

.manual-open-btn {
  margin-top: 20px;
  background: #3b82f6;
  color: #ffffff;
  border: none;
  padding: 10px 20px;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}

.manual-open-btn:hover { background: #2563eb; }

/* Modal Styles */
.palette-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(15, 23, 42, 0.4);
  backdrop-filter: blur(8px);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 15vh;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease, visibility 0.2s ease;
}

.palette-backdrop.is-open {
  opacity: 1;
  visibility: visible;
}

.palette-modal {
  width: 100%;
  max-width: 600px;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(20px);
  border-radius: 16px;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
  border: 1px solid rgba(255, 255, 255, 0.5);
  overflow: hidden;
  transform: scale(0.95);
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.palette-backdrop.is-open .palette-modal {
  transform: scale(1);
}

.palette-header {
  display: flex;
  align-items: center;
  padding: 16px 20px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.search-icon {
  color: #64748b;
  margin-right: 12px;
}

.palette-input {
  flex-grow: 1;
  border: none;
  background: transparent;
  font-size: 1.15rem;
  color: #0f172a;
  outline: none;
  font-family: inherit;
}

.palette-input::placeholder {
  color: #94a3b8;
}

.esc-hint {
  background: #f1f5f9;
  color: #64748b;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #e2e8f0;
}

.palette-body {
  padding: 12px;
  max-height: 400px;
  overflow-y: auto;
}

.palette-group-title {
  font-size: 0.75rem;
  font-weight: 700;
  color: #94a3b8;
  margin: 12px 0 8px 8px;
  letter-spacing: 1px;
}

.palette-item {
  display: flex;
  align-items: center;
  padding: 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.1s;
}

.palette-item:hover, .palette-item.active {
  background: #3b82f6;
  color: #ffffff;
}

.item-icon {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  margin-right: 12px;
  color: #ffffff;
}

.blue-icon { background: #3b82f6; }
.purple-icon { background: #a855f7; }
.green-icon { background: #10b981; }

.palette-item.active .item-icon {
  background: #ffffff;
}
.palette-item.active .blue-icon { color: #3b82f6; }
.palette-item.active .purple-icon { color: #a855f7; }
.palette-item.active .green-icon { color: #10b981; }

.item-name {
  font-weight: 500;
  flex-grow: 1;
  color: inherit;
}

.palette-item:not(.active) .item-name {
  color: #1e293b;
}

.item-shortcut {
  color: #94a3b8;
  font-size: 1.2rem;
}

.palette-item.active .item-shortcut {
  color: #bfdbfe;
}
const backdrop = document.getElementById("cmd-backdrop");
const openBtn = document.getElementById("open-palette-btn");
const searchInput = document.querySelector(".palette-input");

function openPalette() {
  backdrop.classList.add("is-open");
  searchInput.focus();
}

function closePalette() {
  backdrop.classList.remove("is-open");
  searchInput.blur();
}

openBtn.addEventListener("click", openPalette);

/* Listen for Cmd+K or Ctrl+K securely */
window.addEventListener("keydown", function(e) {
  if ((e.metaKey || e.ctrlKey) && e.key === "k") {
    e.preventDefault();
    if (backdrop.classList.contains("is-open")) {
      closePalette();
    } else {
      openPalette();
    }
  }
  
  if (e.key === "Escape" && backdrop.classList.contains("is-open")) {
    closePalette();
  }
});

/* Close when clicking outside modal */
backdrop.addEventListener("click", function(e) {
  if (e.target === backdrop) {
    closePalette();
  }
});
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save