Command palette

A Spotlight-style launcher opened with ⌘K (or Ctrl-K). The global hotkey is a .window modifier on the root; inside the dialog, arrow keys and Enter run off the filtered command list.

Markup

<div
  data-component="palette"
  @keydown.meta.k.window.prevent="toggle"
  @keydown.ctrl.k.window.prevent="toggle"
>
  <button @click="openIt">Search commands <kbd>⌘K</kbd></button>

  <div data-show="open" @click.self="close">
    <div>
      <input
        data-ref="input"
        data-model="query"
        @input="reset"
        @keydown.down.prevent="move(1)"
        @keydown.up.prevent="move(-1)"
        @keydown.enter.prevent="run"
        @keydown.escape="close"
      />
      <ul role="listbox">
        <template data-each="filtered()" data-key="id">
          <li role="option" @click="pick" data-bind="class:rowClass($index), data-i:$index">
            <span data-text="item.label"></span>
            <span data-text="item.group"></span>
          </li>
        </template>
      </ul>
    </div>
  </div>
</div>

Component

Micra.define("palette", {
  state: {
    open: false,
    query: "",
    active: 0,
    last: "",
    commands: [
      { id: 1, label: "Go to Dashboard", group: "Navigate" },
      { id: 2, label: "Create new user", group: "Action" },
      { id: 3, label: "Open settings", group: "Navigate" },
      // ...
    ],
  },
  filtered() {
    const q = this.state.query.trim().toLowerCase();
    return this.state.commands.filter((c) => c.label.toLowerCase().includes(q));
  },
  rowClass(i) {
    return i === this.state.active ? "bg-indigo-600 text-white" : "hover:bg-zinc-100";
  },
  async openIt() {
    this.state.open = true;
    this.state.query = "";
    this.state.active = 0;
    await this.tick(); // the panel is rendered (no longer display: none)
    this.refs.input.focus();
  },
  toggle() {
    this.state.open ? this.close() : this.openIt();
  },
  close() {
    this.state.open = false;
  },
  reset() {
    this.state.active = 0;
  },
  move(d) {
    const n = this.filtered().length;
    if (!n) return;
    this.state.active = (this.state.active + d + n) % n;
  },
  run() {
    const c = this.filtered()[this.state.active];
    if (c) {
      this.state.last = c.label;
      this.state.open = false;
    }
  },
  pick(e) {
    const c = this.filtered()[Number(e.currentTarget.dataset.i)];
    if (c) {
      this.state.last = c.label;
      this.state.open = false;
    }
  },
});

@click.self closes the dialog only when the backdrop itself is clicked, not when the click bubbles up from inside the panel — so selecting a command and dismissing the overlay stay distinct.

⌘K and Ctrl+K are two modifiers on the root: .window listens on window, the .meta/.ctrl and .k guards match the shortcut, and .prevent stops the browser’s own handling only when it matched. openIt waits for the render with await this.tick() before focusing the input; right after open = true the panel is still display: none and focus() would do nothing (Micra 2.9+).