# TallStackUI: Command Palette > TallStackUI is a TALL Stack (Tailwind CSS, Alpine.js, Laravel, Livewire) > component library providing 65+ Blade components for building modern web interfaces. A searchable command palette overlay that fetches results from a server endpoint, supporting keyboard navigation, images, icons, descriptions, grouped results, and disabled options. Triggered by a configurable keyboard shortcut (default: Ctrl+K). Search input is debounced (300ms) to avoid excessive API calls. ## Usage Patterns The command palette supports two usage patterns: ### Global Usage (Layout-Level) Place the component in your application layout (e.g., `resources/views/components/layouts/app.blade.php`) for app-wide access. Combine with an `actionable` class in config for server-side selection handling: ```blade {{-- resources/views/components/layouts/app.blade.php --}} {{ $slot }} ``` ```php // config/tallstackui.php 'command-palette' => [ TallStackUi\Components\CommandPalette\Component::class, [ 'actionable' => App\Actions\CommandPaletteAction::class, 'request' => '/api/search', // ... ], ], ``` ### Page-Specific Usage (Inline Events) Place the component on specific pages and handle selection with inline `x-on:select`: ```blade ``` ### Multiple Instances Use the `id` attribute to place multiple command palettes on the same page and target them independently: ```blade Search Actions ``` ## Basic Usage ```blade ``` ```blade ``` ```blade
Nothing here.
``` ```blade {{-- Centered: vertically centers on mobile with fully rounded corners --}} ``` ## Attributes | Attribute | Type | Default | Description | |--------------|---------------------|-----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| | id | string\|null | 'command-palette' | Unique identifier for targeting with `$tsui.open.commandPalette(id)`. Required when using multiple palettes | | request | string\|array\|null | null (from config) | Data source URL (string, route name, or array with `url`, `method`, `params` keys) | | options | Collection\|array | [] | Static options array (each item should have label, value, and optionally description, image, icon) | | selectable | array\|null | [] | Parsed field mapping (auto-generated from `select`) | | placeholders | array\|null | null | Override default placeholder texts (keys: `search`, `empty`, `navigate`, `select`, `close`) | | recycle | bool\|null | true (from config) | When true, preserves previous search results when reopening the palette | | shortcut | string\|null | 'ctrl.k' (from config) | Keyboard shortcut in dot notation (e.g., `ctrl.k`, `ctrl.shift.p`, `meta.k`). Inline overrides config | | select | string\|null | 'label:label\|value:value\|description:description\|image:image\|icon:icon' | Field mapping string for option data (format: `label:key\|value:key\|description:key\|image:key\|icon:key`) | | centered | bool\|null | false (from config) | When true, centers the palette vertically on mobile with rounded corners on all sides | ## Slots | Slot | Description | |-------|-----------------------------------------------------------------------------------------| | empty | Custom content displayed when search yields no results (replaces default empty message) | ## Validation Constraints - The `request` attribute must be configured either as an inline attribute or in the config file. - When `request` is an array, the `url` key is required. - When `request` is an array with a `method` key, it must be `get` or `post`. - When `request` is an array with a `params` key, it must be a non-empty array. - When `actionable` is set in config, the class must exist and be invocable (`__invoke`). ## Option Features ### Disabled Options Options can be marked as disabled in the API response. Disabled options are displayed with muted styles and cannot be selected: ```json [ { "label": "Active Item", "value": 1 }, { "label": "Unavailable Item", "value": 2, "disabled": true } ] ``` ## Selection Handling When a user selects an option, the component uses a priority chain to determine how to handle it: 1. **Inline event** (`x-on:select`) — If the component has an `x-on:select` listener, dispatches via Alpine's `$dispatch()` (component-scoped, not window). The actionable and global event are suppressed. 2. **Actionable** (`config actionable`) — If an actionable class is configured, sends a POST request to a Laravel signed route. The server invokes the class and returns a `Callback` response (redirect or event). 3. **Global event** (fallback) — Dispatches a `command-palette:{id}:select` window event with the selected option data. In all cases, internal keys prefixed with `__` are stripped from the option data before dispatching. ### Inline Event (x-on:select) ```blade ``` The `$event.detail` contains all fields from the selected option (internal keys prefixed with `__` are stripped). ### Actionable (Server-Side Action) Configure an invocable class in `config/tallstackui.php`: ```php 'command-palette' => [ TallStackUi\Components\CommandPalette\Component::class, [ 'actionable' => App\Actions\CommandPaletteAction::class, // ... ], ], ``` The class receives an `ItemSelected` value object and must return a `Callback`: ```php use TallStackUi\Support\CommandPalette\Callback; use TallStackUi\Support\CommandPalette\ItemSelected; class CommandPaletteAction { public function __invoke(ItemSelected $selected): Callback { return Callback::redirect("/items/{$selected->value}"); } } ``` The actionable endpoint uses Laravel's signed URLs (`URL::signedRoute()`) for security. The controller validates the signature with `abort_unless($request->hasValidSignature(), 403)`. #### ItemSelected Value Object Immutable DTO implementing `Arrayable`. All properties are `readonly`. | Property | Type | Description | |-------------|---------|--------------------------------------| | search | string | The search term at time of selection | | label | mixed | The selected option's label | | value | mixed | The selected option's value | | description | ?string | Optional description text | | image | ?string | Optional image URL | | icon | ?string | Optional icon HTML | | additional | array | Extra fields from the API response | `toArray()` returns all properties as an associative array. #### Callback Response Object ```php // Redirect to an internal page Callback::redirect('/dashboard'); // Redirect to an external URL (opens in new tab) Callback::redirect('https://example.com')->external(); // Redirect using Livewire.navigate (SPA-style navigation) Callback::redirect('/dashboard')->navigate(); // Dispatch a browser event Callback::event('item-selected'); // Dispatch a browser event with parameters Callback::event('item-selected')->with(['id' => $selected->value]); ``` The JavaScript receives the full callback response structure: ```js { type: 'redirect' | 'event', data: { to: '...' } | { name: '...', params: {...} }, external: boolean, navigate: boolean } ``` When `navigate` is `true`, the redirect uses `Livewire.navigate()` for SPA-style navigation without a full page reload. Falls back to `window.location.href` if Livewire is not available. ### Global Event (Fallback) When no inline `x-on:select` or actionable is configured: ```blade {{-- Default id --}}
{{-- Custom id --}}
``` ## Lifecycle Events Open/close events are always dispatched regardless of selection mode. Window events include the component's `id` in the event name: | Event | Channel | Trigger | |------------------------------|-------------|----------------| | `open` (inline) | `$dispatch` | Palette opens | | `close` (inline) | `$dispatch` | Palette closes | | `command-palette:{id}:open` | `window` | Palette opens | | `command-palette:{id}:close` | `window` | Palette closes | ```blade {{-- Inline lifecycle events --}} {{-- Global lifecycle events (default id) --}}
{{-- Global lifecycle events (custom id) --}}
``` ## Keyboard Navigation The component supports full keyboard navigation: - **Arrow Up/Down** — Navigate through search results, auto-scrolling into view - **Enter** — Select the highlighted option - **Escape** — Close the palette - Mouse hover updates the highlighted option, but does not conflict with keyboard navigation (the component tracks input mode internally) ## Configuration In `config/tallstackui.php` under `components.command-palette`: | Option | Type | Default | Description | |------------|---------------------|----------|--------------------------------------------------------------------------------------------| | actionable | string\|null | null | Invocable PHP class for server-side action handling | | request | string\|array\|null | null | Default data source for all command palettes | | z-index | string | 'z-50' | Default z-index class | | blur | bool\|string | false | Background blur effect (`false` disables, `true` defaults to 'sm', or 'sm'/'md'/'lg'/'xl') | | overflow | bool | false | When true, avoids hiding body overflow | | shortcut | string | 'ctrl.k' | Keyboard shortcut in dot notation (e.g., `ctrl.k`, `ctrl.shift.p`, `meta.k`) | | recycle | bool | true | When true, preserves previous results when reopening | | elements | bool | true | When true, shows keyboard hint elements in the footer | | scrollbar | bool | true | When true, applies a custom minimal scrollbar to the results list | | centered | bool | false | When true, centers the palette vertically on mobile with all corners rounded | ## JavaScript Control ```js // Default (targets id="command-palette") $tsui.open.commandPalette() $tsui.close.commandPalette() // Target specific palette by ID $tsui.open.commandPalette('search') $tsui.close.commandPalette('search') ``` ## Soft Customization Soft customization allows you to override default Tailwind CSS classes used by this component at runtime, either through a service provider or scoped per-instance. ### Customization ```php TallStackUi::customize() ->commandPalette() ->block('backdrop', 'your-tailwind-classes'); ``` ### Available Blocks | Block Name | Purpose | |--------------------|---------------------------------------------------| | backdrop | Fixed overlay background behind the palette | | blur.sm | Small backdrop blur effect | | blur.md | Medium backdrop blur effect | | blur.lg | Large backdrop blur effect | | blur.xl | Extra-large backdrop blur effect | | wrapper | Fixed container that positions the palette | | positions.bottom | Bottom-aligned position classes (default) | | positions.center | Center-aligned position classes (centered mode) | | box | Main palette card with shadow | | box.radius.default | Border radius for default (bottom) position | | box.radius.center | Border radius for centered position | | input.wrapper | Flex container for the search input area | | input.icon | Search magnifying glass icon styles | | input.base | Search text input field styles | | input.loading | Loading spinner container | | list | Scrollable results list container | | option.base | Base styles for each result option | | option.active | Active/highlighted option styles | | option.disabled | Disabled option styles | | option.image | Option image (avatar) styles | | option.icon | Option icon container styles | | option.content | Option text content wrapper | | option.label | Option label text styles | | option.description | Option description text styles | | empty | Empty state message styles | | footer | Keyboard hints footer container |