Command Palette
Command Palette component.
Command Palette, Customization Blocks
Example:
// AppServiceProvider, "boot" method. TallStackUi::customize() ->commandPalette() ->block('block', 'classes');
Many modern applications use a command palette to help users quickly find and navigate
to anything (pages, actions, contacts, or settings) without leaving the keyboard.
The command-palette
component provides exactly that: searchable overlay
that fetches results from your server, supports keyboard navigation, and can display
images, icons, and descriptions alongside each result. Place it once in your layout for
app-wide access using a keyboard shortcut (default: Ctrl+K
), or use it
on specific pages with inline event handling.
<x-command-palette id="search" :request="route('api.users')" select="label:name|value:id" /> <x-button x-on:click="$tsui.open.commandPalette('search')"> Open Command Palette</x-button>
When you press enter to select an option, the component dispatches a select
event with the selected
option's value in the event.detail.value
property. Continue reading to learn about all the ways to
interact with the component and handle selections.
The command palette opens when the user presses the configured keyboard shortcut. The default is
Ctrl
+ K
, but you can change it inline or globally in the
configuration file
using dot notation: ctrl.k
, ctrl.shift.p
, meta.k
, or even
inline using the shortcut
attribute with the same dot notation.
The request
attribute defines where the component fetches search results
from. You can use a simple URL string or a Laravel route name:
<!-- Simple URL string --><x-command-palette request="/api/search" /> <!-- Using a route name --><x-command-palette request="api.users" /> <!-- Using full route path --><x-command-palette :request="route('api.users')" />
For more control, pass an array with url
, method
, and
params
keys:
<!-- Array with url, method, and params --><x-command-palette :request="[ 'url' => '/api/search', 'method' => 'post', 'params' => ['category' => 'users'],]" />
The request
attribute must be configured either as an inline attribute or
in the configuration file. When using an array, the url
key is required and
method
must be get
or post
.
The select
attribute maps your API response fields to the component's
internal structure. The format is similar to the select.styled
component:
<x-command-palette id="users" request="/api/users" select="label:name|value:id|description:email|image:avatar" /> <x-button x-on:click="$tsui.open.commandPalette('users')"> Search Users</x-button>
Options can be marked as disabled in the API response. Disabled options are displayed with muted styles and cannot be selected:
[ { "name": "Active User", "id": 1 }, { "name": "Inactive User", "id": 2, "disabled": true }]
Talking about the API response, you can also set an extra field called additional
to include
any other data from your API response in array format. This additional data will be available in the selection
event, allowing you to use it for various purposes.
By default, the component preserves previous search results when the palette is reopened. Use the
recycle
attribute to control this behavior:
<!-- Preserves previous results when reopening (default: true) --><x-command-palette request="/api/search" /> <!-- Clears results every time the palette opens inline --><x-command-palette request="/api/search" :recycle="false" />
You can also control it globally in the configuration file.
Override the default placeholder texts using the placeholders
attribute.
Available keys are: search
, empty
,
navigate
, select
, and close
:
<!-- Available keys: search, empty, navigate, select, close --><x-command-palette request="/api/search" :placeholders="[ 'search' => 'Type to search...', 'empty' => 'Nothing found.', ]" />
<x-command-palette id="search" request="/api/search"> <x-slot:empty> <div class="flex flex-col items-center gap-2 p-8"> <x-icon name="magnifying-glass" class="h-8 w-8 text-gray-400" /> <p class="text-sm text-gray-500">No results match your search.</p> </div> </x-slot:empty></x-command-palette> <x-button x-on:click="$tsui.open.commandPalette('search')"> Open Command Palette</x-button>
You have three different ways to interact with an item selection. The simplest way is to set x-on:select
to handle the selection with component scope. When present, this option has the highest priority and suppresses both the actionable and global events.
<x-command-palette id="search" request="/api/users" select="label:name|value:id" x-on:select="alert('Selected: ' + $event.detail.label)" /> <x-button x-on:click="$tsui.open.commandPalette('search')"> Open Command Palette</x-button>
Since you might want to use the component globally, like in a layout file, you can interact with item selection in two other ways. You can configure an invocable PHP class in the configuration file to handle selections on the server side. This way, selecting an item will go through an internal TallStackUI route to handle the action of creating the instance of your class and invoking it through the Laravel container. The internal TallStackUI endpoint uses Laravel-signed URLs for added security.
// ... 'command-palette' => [ Components\CommandPalette\Component::class, /* |---------------------------------------------------------------------- | Command Palette Settings |---------------------------------------------------------------------- | | actionable: the callable class for handling item selection (e.g., App\Support\GlobalSearch::class). | request: the data source for the command palette. | z-index: controls the default z-index. | blur: enables the background blur effect (Allowed: false, sm, md, lg, xl). | overflow: avoids hiding the overflow, allowing the scroll of the page. | shortcut: keyboard shortcut to toggle the palette (e.g., 'ctrl.k', 'ctrl.shift.p'). | recycle: when true, preserves previous results when reopening the palette. | elements: when true, shows the keyboard hints in the footer. | scrollbar: when true, applies a custom minimal scrollbar to the results list. | centered: when true, centers the palette vertically on mobile with fully rounded corners. */ [ 'actionable' => App\Actions\CommandPaletteAction::class, 'request' => null, 'z-index' => 'z-50', 'blur' => false, 'overflow' => false, 'shortcut' => 'ctrl.k', 'recycle' => true, 'elements' => true, 'scrollbar' => true, 'centered' => false, ],], // ...
The class receives an ItemSelected
value object and must return a
Callback
response:
use TallStackUi\Support\CommandPalette\Callback;use TallStackUi\Support\CommandPalette\ItemSelected; class CommandPaletteAction{ public function __invoke(ItemSelected $selected): Callback { return Callback::redirect("/users/{$selected->value}"); }}
The ItemSelected
object provides access to all selection data:
// ItemSelected properties: $selected->search; // string — the search term$selected->label; // mixed — option label$selected->value; // mixed — option value$selected->description; // ?string — optional description$selected->image; // ?string — optional image URL$selected->icon; // ?string — optional icon HTML$selected->additional; // array — extra fields from the API
The Callback
class offers two response types: redirect the user to a page (internal ou external)
or dispatch a browser event:
namespace App\Actions; use TallStackUi\Support\CommandPalette\Callback;use TallStackUi\Support\CommandPalette\ItemSelected; class CommandPaletteAction{ public function __invoke(ItemSelected $selected): Callback { // Redirect to an internal page return Callback::redirect("/users/{$selected->value}"); // Redirect to an external URL (opens in new tab) return Callback::redirect('https://example.com')->external(); // Redirect using Livewire.navigate (SPA-style navigation) return Callback::redirect('/dashboard')->navigate(); // Dispatch a browser event return Callback::event('user-selected'); // Dispatch a browser event with parameters return Callback::event('user-selected')->with(['id' => $selected->value]); }}
The component triggers opening and closing events, as well as a global event when something is selected
– this is the third option available for handling item selection, regardless of the selection mode.
You can listen for them inline or globally. The window's global events include the component's id
in the event name:
<!-- Inline lifecycle events --><x-command-palette id="search" request="/api/search" x-on:open="console.log('opened')" x-on:close="console.log('closed')" /> <!-- Global lifecycle events (event name includes the id) --><div x-on:command-palette:search:select.window="console.log($event.detail)" x-on:command-palette:search:open.window="console.log('opened')" x-on:command-palette:search:close.window="console.log('closed')"> <x-command-palette id="search" request="/api/search" /></div>
Helpers to open and close the command palette using AlpineJS.
<x-command-palette id="search" request="/api/search" /> <!-- Open by id --><x-button x-on:click="$tsui.open.commandPalette('search')"> Open</x-button> <!-- Close by id --><x-button x-on:click="$tsui.close.commandPalette('search')"> Close</x-button>
Since you generally won't want to change this all the time, the blur setting is defined
exclusively via a configuration file,
with four available variables: false
, sm
, md
, and lg
. The default is md
.
By default, the command palette is aligned to the bottom of the screen on mobile devices. You can change this behavior
in the configuration file using the centered
configuration.
When set to true, the command palette will be centered on mobile devices.