🎉 Welcome to the new major version: v3. Upgrade now!

Powerful suite of Blade components for TALL Stack apps.

Command Palette

Command Palette component.

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.

No results found.

<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.',
]" />

No results match your search.

<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.

Code highlighting provided by Torchlight