The command palette (Spotlight / Ctrl+K style) ships as a stateful LiveComponent plus a behaviour you implement. The reusable interaction — modes, the multi-step wizard, pagination, keyboard navigation and the global Ctrl+K / ⌘K shortcut — lives in the component; everything that differs per project — your commands, contexts, search and actions — is injected through a PureAdmin.CommandPalette.Source module.

Ver la demostración interactiva
Mount the component once, in your layout. Because it lives in the layout, Ctrl+K and the navbar/sidebar search triggers work on every page — there is no per-page setup and no per-trigger event plumbing.

1 · Móntalo en tu layout

One instance, referenced by id. The source is a module (below) that supplies your app's data and actions.

# In your app layout (e.g. lib/my_app_web/components/layouts/app.html.heex),
# mount the palette ONCE so Ctrl+K and every search trigger work app-wide:
<.live_component
  module={PureAdmin.CommandPalette}
  id="command-palette"
  source={MyApp.Palette}
/>

2 · Conecta los disparadores

show_command_palette/0 is a JS command — attach it to anything. It dispatches to the palette by id, so no LiveView event is needed.

# Any trigger opens it — no per-trigger LiveView wiring. `show_command_palette/0`
# dispatches `pa:command-palette:open` to the palette element by id.
<.navbar_search phx-click={show_command_palette()} />
<.sidebar_search phx-click={show_command_palette()} />

# ...or from your own button:
<.button phx-click={show_command_palette()}>Search…</.button>

Contrólalo desde un LiveView

Open it pre-filled, or change the display style, with send_update/2.

# Drive the global palette from any LiveView via send_update/2:
def handle_event("search", _params, socket) do
  send_update(PureAdmin.CommandPalette, id: "command-palette", open_with: "/")
  {:noreply, socket}
end

# Switch the step display style ("inline" | "tokens"):
send_update(PureAdmin.CommandPalette, id: "command-palette", display: "tokens")

3 · Implementa un Source

use PureAdmin.CommandPalette.Source gives overridable no-op defaults — define only the callbacks you need.

defmodule MyApp.Palette do
  use PureAdmin.CommandPalette.Source

  # Commands appear under "/" and on the idle home screen.
  def commands do
    [
      %{
        id: "go",
        shortcut: "/go",
        aliases: ["/g"],
        hotkey: "Alt+G",
        name: "Go to Page",
        description: "Jump to any page",
        icon: "🧭",
        steps: [
          %{id: "page", prompt: " ", placeholder: "Type a page…", free_text: true}
        ]
      }
    ]
  end

  # Contexts appear under ":" for scoped search.
  def contexts do
    [%{id: "users", shortcut: ":users", aliases: [":u"], name: "Users", icon: "👤"}]
  end

  # Options for a command step, already filtered by `query`.
  def step_options("go", "page", query, _selections), do: search_pages(query)
  def step_options(_command, _step, _query, _selections), do: []

  # Search results for a scope, already filtered by `query`.
  def search(:global, query), do: search_everything(query)
  def search({:context, "users"}, query), do: search_users(query)
  def search(_scope, _query), do: []

  # What a selection / a finished command DOES — a declarative directive.
  def on_select(item), do: {:navigate, item.href}
  def on_complete("go", selections), do: {:navigate, List.last(selections).value}
  def on_complete(_command, _selections), do: :close
end

Callbacks de origen

The contract in PureAdmin.CommandPalette.Source. Filtering is the source's job — search/2 and step_options/4 receive the query and return the already-filtered list; the component paginates and, in global search, prepends the commands/contexts that match.

CallbackDevolucionesPropósito
commands/0 [command] Commands shown under / and on the home screen
contexts/0 [context] Search contexts shown under :
step_options/4 [option] Options for (command_id, step_id, query, selections)
search/2 [item] Results for a scope (:global | {:context, id}) + query
on_select/1 directive What selecting a search result does
on_complete/2 directive What finishing a command does (command_id, selections)

Directivas

on_select/1 and on_complete/2 return a directive the component executes — so the palette can live globally without any LiveView carrying selection handlers.

DirectivaEfecto
{:navigate, path} push_navigate (closes the palette first)
{:patch, path} push_patch
{:toast, variant, title, msg} flash a toast (opts as a 5th element)
:close just close the palette
:noop / nil do nothing

Formas de datos

TipoForma
command %{id, shortcut, aliases, hotkey, name, description, icon, steps}
step %{id, prompt, placeholder, free_text}
context %{id, shortcut, aliases, name, description, icon}
option %{label, value, description, icon, code}
item %{title, subtitle, icon, badge, …your keys}

free_text on a step lets the user submit typed text when nothing matches. A search item can carry any extra keys your on_select/1 needs (e.g. :href).

Modos y teclado

Modos

PrefijoModoDescripción
/ Commands Multi-step action wizards
: Search Scoped entity search
(none) Global Search everything

Teclado

ClaveAcción
Ctrl+K / ⌘K Toggle palette
↑ ↓ Navigate results
← → Previous / next page (search modes)
Enter / Tab Select or confirm free text
Backspace (at start) Back to previous step
Esc Back (in step/context) or close

John Doe

Administrator

Configuración

Press Ctrl+K to preview the palette.
Sidebar
Display
Profile Panel
Body text size. All elements scale proportionally.
Comandos
🚀
Desplegar a entorno
Desplegar una rama a un entorno
g d
👤
Asignar a usuario
Asignar un elemento a un miembro del equipo
g a
🧭
Ir a la página
Navegar a una página
g g
🎨
Cambiar tema
Cambiar el tema visual
g t
Buscar
📦
Productos
Buscar productos
:products
📋
Pedidos
Buscar pedidos
:orders
👥
Usuarios
Buscar usuarios
:users
🧾
Facturas
Buscar facturas
:invoices