Command palette

⌘K-style launcher. Document-level shortcut opens the overlay, typed text filters the registered commands, arrow keys move the highlight, Enter runs the command. The visible list is a filtered() method — registered commands stay in state.commands.

Live preview

Press ⌘K or Ctrl K — or use the button below.

Last command:

Markup

<div data-component="cmdk-demo">
  <div class="cmdk-backdrop" data-if="visible" @click.self="close">
    <div class="cmdk-panel" role="dialog" aria-modal="true">
      <div class="cmdk-search">
        <input data-ref="input"
               data-model="query"
               @keydown="onKey"
               placeholder="Type a command…" />
      </div>
      <div class="cmdk-list">
        <template data-each="filtered()" data-key="id">
          <button class="cmdk-row"
                  @mouseenter="setHighlight"
                  @click="run"
                  data-bind="data-id:item.id, data-i:$index"
                  data-class="highlight:$index === highlight">
            <span data-text="item.label"></span>
            <span data-text="item.group"></span>
          </button>
        </template>
        <p data-if="filtered().length === 0">No matches</p>
      </div>
    </div>
  </div>
</div>

Definition

Micra.define('cmdk-demo', {
  state: {
    visible: false,
    query: '',
    highlight: 0,
    lastRan: '',
    commands: [
      { id: 'new-doc',  label: 'New document',  group: 'Files',    icon: 'icon-document-text' },
      { id: 'search',   label: 'Search files',  group: 'Files',    icon: 'icon-magnifying-glass' },
      { id: 'invite',   label: 'Invite teammate', group: 'Team',  icon: 'icon-bolt' },
      { id: 'theme',    label: 'Toggle theme',  group: 'Appearance', icon: 'icon-bolt' },
      { id: 'shortcuts',label: 'Show keyboard shortcuts', group: 'Help', icon: 'icon-command-line' },
      { id: 'logout',   label: 'Sign out',      group: 'Account',  icon: 'icon-bolt' },
    ],
  },

  filtered() {
    const q = this.state.query.trim().toLowerCase()
    if (!q) return this.state.commands
    return this.state.commands.filter(c =>
      c.label.toLowerCase().includes(q) || c.group.toLowerCase().includes(q),
    )
  },

  onCreate() {
    this._shortcut = (e) => {
      const isCmdK = (e.metaKey || e.ctrlKey) && e.key === 'k'
      if (isCmdK) {
        e.preventDefault()
        this.open()
      }
    }
    document.addEventListener('keydown', this._shortcut)
  },
  onDestroy() {
    document.removeEventListener('keydown', this._shortcut)
  },

  open() {
    this.state.visible = true
    this.state.query = ''
    this.state.highlight = 0
    queueMicrotask(() => this.refs.input?.focus())
  },
  close() { this.state.visible = false },

  setHighlight(e) {
    const i = parseInt(e.currentTarget.dataset.i, 10)
    if (!Number.isNaN(i)) this.state.highlight = i
  },

  onKey(e) {
    const list = this.filtered()
    if (e.key === 'ArrowDown') {
      e.preventDefault()
      this.state.highlight = (this.state.highlight + 1) % list.length
    } else if (e.key === 'ArrowUp') {
      e.preventDefault()
      this.state.highlight = (this.state.highlight - 1 + list.length) % list.length
    } else if (e.key === 'Enter') {
      e.preventDefault()
      const cmd = list[this.state.highlight]
      if (cmd) this._run(cmd)
    } else if (e.key === 'Escape') {
      this.close()
    }
  },

  run(e) {
    const id = e.currentTarget.dataset.id
    const cmd = this.state.commands.find(c => c.id === id)
    if (cmd) this._run(cmd)
  },

  _run(cmd) {
    this.state.lastRan = cmd.label
    this.close()
    Micra.emit('command:run', cmd)
  },
})

Integration

  • Global shortcut. A single document keydown listener in onCreate matches ⌘K / Ctrl+K. Removed in onDestroy so detached instances stop responding.
  • Focus on open. queueMicrotask(() => this.refs.input.focus()) waits for the data-if="visible" render to attach the input to the DOM, then focuses it.
  • Commands publish on the bus. Micra.emit('command:run', cmd) — wire any other component to this.on('command:run', …) and dispatch on cmd.id.
  • Filtering matches both label and group, so a user can type "team" to find "Invite teammate".

Pitfalls

  • Don't bind @keydown.enter on the input — Enter must compete with arrow / Escape handling. One onKey branching on e.key beats four separate modifier-suffixed handlers.
  • Don't mount more than one cmdk-demo instance — both will register the ⌘K listener and open simultaneously.
  • Don't store filtered in state. Query updates already trigger a re-render; the method recomputes on read.