Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Spotlight Laravel Package

wire-elements/spotlight

Wire Elements Spotlight is a Livewire component that brings Spotlight/Alfred-style command palette search to your Laravel app. Open it with CTRL+K or CMD+K to quickly search and run commands, all powered by Livewire.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config:

composer require wire-elements/spotlight
php artisan vendor:publish --tag=livewire-ui-spotlight-config

Add the component once, near the end of your layout (it renders a hidden overlay, so position doesn't matter much, but it must be present on every page you want the palette available):

<body>
    <!-- content -->

    @livewire('livewire-ui-spotlight')
</body>

Only if you're on Livewire v2, also include Alpine (v3 bundles it — loading it twice will break your app):

<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>

Scaffold your first command and register it:

php artisan make:spotlight Logout
class Logout extends SpotlightCommand
{
    protected string $name = 'Logout';
    protected string $description = 'Log out of your account';

    public function execute(Spotlight $spotlight, StatefulGuard $guard): void
    {
        $guard->logout();
        $spotlight->redirect('/');
    }
}
// config/livewire-ui-spotlight.php
'commands' => [
    \App\SpotlightCommands\Logout::class,
],

Hit CMD/CTRL + K and type "log". That's the whole loop — the first use case to ship is almost always a navigation palette: 10–20 commands that just $spotlight->redirect(...) to your most-visited screens. It's low risk, immediately demoable, and forces you to learn registration, shouldBeShown, and synonyms before you touch dependency chains.

First places to look after install: app/SpotlightCommands/ (create it and namespace consistently), config/livewire-ui-spotlight.php for shortcuts/commands/CSS/JS toggles, and the published translations file if you want a non-English placeholder.

Implementation Patterns

Navigation commands — the bread and butter. One class per destination, name it the way a user would say it, add $synonyms for everything else they might type:

class ViewBillingSettings extends SpotlightCommand
{
    protected string $name = 'View Billing Settings';
    protected string $description = 'Update your billing settings';
    protected array $synonyms = ['subscription', 'credit card', 'payment'];

    public function execute(Spotlight $spotlight): void
    {
        $spotlight->redirect('/settings/billing');
    }
}

Synonyms are the highest-leverage field in the package. Users never type your $name; they type "invoices", "plan", "card". Budget time for this or adoption stalls.

Action commands that open UI instead of acting. For anything with more than one field, don't try to model the form inside Spotlight. Resolve a dependency, then hand off to a modal:

public function execute(Spotlight $spotlight, Team $team): void
{
    $spotlight->emit('openModal', 'user-create', ['team' => $team->id]);
}

This keeps the command class a dispatcher, not a controller, and makes destructive actions safe — the user still sees a confirmation surface.

Parameterized commands via dependencies. Dependencies are an ordered chain; each search{Name} method gets the user's query plus any already-resolved dependencies, which lets you scope the second search by the first:

public function dependencies(): ?SpotlightCommandDependencies
{
    return SpotlightCommandDependencies::collection()
        ->add(SpotlightCommandDependency::make('team')
            ->setPlaceholder('For which team?'))
        ->add(SpotlightCommandDependency::make('role')
            ->setPlaceholder('Which role?')
            ->setType(SpotlightCommandDependency::INPUT));
}

public function searchTeam($query)
{
    return Team::where('name', 'like', "%{$query}%")
        ->limit(10)
        ->get()
        ->map(fn (Team $team) => new SpotlightSearchResult(
            $team->id,
            $team->name,
            "Create user for {$team->name}"
        ));
}

public function searchRole($query, Team $team) { /* scoped by $team */ }

Use INPUT-typed dependencies when the answer is free text (a name, an email) and result-returning dependencies when the answer must be picked from your data. Chained dependencies are the feature that separates Spotlight from a glorified bookmarks bar — worth investing in for 3–5 power commands.

Registration strategy. Config file for static, always-on commands; service provider for anything environment- or feature-flag-driven:

// AppServiceProvider::boot()
Spotlight::registerCommand(CreateUser::class);
Spotlight::registerCommandIf(config('features.billing'), ViewBilling::class);
Spotlight::registerCommandUnless(app()->isProduction(), SeedDemoData::class);

Then gate per-request visibility on the command itself — dependencies are resolved from the container, so you get the authenticated user:

public function shouldBeShown(Request $request): bool
{
    return $request->user()?->can('create user') ?? false;
}

Prefer shouldBeShown over conditional registration wherever the condition depends on the current user, tenant, or route. It keeps authorisation next to the command it protects instead of scattered across providers.

Opening it from your own UI. In Livewire v3: $this->dispatch('toggle-spotlight');. In markup: <button @click="$dispatch('toggle-spotlight')">Search</button>. Add a visible trigger in your nav — discoverability of the shortcut is the single biggest adoption factor.

Testing. Commands are plain classes, so test them directly: instantiate, assert shouldBeShown() against a Request with a user, call searchTeam('acme') and assert the SpotlightSearchResult collection, and call execute() with a mocked Spotlight plus fakes for other injected deps. You rarely need a browser test.

Gotchas and Tips

shouldBeShown does not register the command. This catches nearly everyone: hiding logic on the command and forgetting the config/provider entry means the command silently never appears. If a command "doesn't show up", check registration first, then shouldBeShown, then the config cache.

Config cache bites during development. Registering commands in livewire-ui-spotlight.php while php artisan config:cache is active means your new command won't appear until php artisan config:clear. If you iterate on commands a lot, register them in a service provider instead — no cache involved.

Parameter names in execute must line up with dependency identifiers. The value passed in is the identifier you returned in SpotlightSearchResult (usually an ID), not the model. The README's own example is misleading here — it type-hints Team $team and then uses $team->id, which only works if the container can resolve it. In practice, either accept the identifier as a scalar and look the model up, or use INPUT dependencies for plain strings. Don't rely on route-model-binding magic; verify with a quick dd() on your first dependency command.

Search method naming is search + StudlyCase of the dependency name. ->add(...make('team')) maps to searchTeam(). Miss the case and you get an empty result list with no error.

Search methods fire on every keystroke. Keep them cheap: limit(10), select only needed columns, no writes, no events, no Mail::send. A dependency search that runs five joins per character will make the palette feel broken.

Identifiers should be stable strings or ints, cast consistently. If searchTeam returns ints and your execute expects strings (or vice versa), the lookup fails only at execution time, in production, in front of a user.

Tailwind is assumed. If your app isn't on Tailwind, set 'include_css' => true in the config, otherwise the overlay renders unstyled. Conversely, if you are on Tailwind and your build purges aggressively, the Spotlight view classes live outside your content globs — check the palette styling in a production build, not just npm run dev.

Bundling JS. With 'include_js' => false you must npm install --save fuse.js and require('vendor/wire-elements/spotlight/resources/js/spotlight'); in your bundler. Setting the flag false without doing both leaves you with a palette that opens and never responds.

Livewire v2 vs v3 events. dispatchBrowserEvent('toggle-spotlight') is v2; use dispatch('toggle-spotlight') on v3. Same for the commands' internals — README examples use $spotlight->emit(...), which is v2 syntax. If you're on v3 and emit isn't proxied, switch to $spotlight->dispatch(...) / dispatchTo(...).

Shortcut conflicts. Defaults are CMD/CTRL + K and CMD/CTRL + /, both of which browsers and other tooling claim (Firefox quick-search, Chrome's address bar, Safari's sidebar). The shortcuts array takes key names like 'k' and 'slash'; if you have an IDE-ish audience, adding a second binding is cheap insurance. Always ship a clickable trigger alongside the keyboard shortcut.

Don't publish the views speculatively. Once you publish --tag=livewire-ui-spotlight-views you own that template forever and upgrades stop reaching you. Publish translations and config freely; publish views only when you genuinely need to restructure the markup.

Destructive commands deserve a modal. Spotlight has no built-in confirmation step. A command named "Delete Team" that runs execute immediately on Enter is a support ticket waiting to happen — emit an event and let your modal component ask.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin