Search
The search package adds a global application search to Lattice: a command-menu interface backed by
search providers the application registers with the #[AsSearchProvider] attribute. Providers are
picked up by Lattice’s discovery, results are grouped by category, and an optional
SearchHistoryRecorder binding surfaces recent selections.
Installation
Section titled “Installation”composer require lattice-php/searchThat is the whole integration: the package ships its React renderer as source, and the
lattice() Vite plugin compiles it into your app’s bundle via virtual:lattice/plugins
(see Component packages). The PHP classes are picked up by
Lattice’s discovery and TypeScript generation automatically.
No-build apps use the precompiled module the package also ships:
run php artisan lattice:assets after installation.
The search dialog
Section titled “The search dialog”SearchBox::make() renders a trigger button that opens the search dialog (also reachable via
⌘K/Ctrl+K). The dialog opens compact — just the input, plus recent selections when a
SearchHistoryRecorder is bound — and expands into the three-pane layout (categories, results,
preview) once a query is typed. Inside a collapsed sidebar the trigger shrinks to an icon-only
button; the keyboard shortcut keeps working.
Pass your own ->schema([...]) of SearchInput, SearchResults, SearchCategories,
SearchRecent, and SearchPreview components to replace the default composition entirely.
From your own React code
Section titled “From your own React code”The same pieces are props-based clients exported from @lattice-php/search, so an app can mount
the trigger outside a Lattice page or compose its own palette:
import { SearchBox, SearchInput, SearchResults } from "@lattice-php/search";
<SearchBox endpoint="/lattice/search" placeholder="Find anything" shortcut> <SearchInput /> <SearchResults /></SearchBox>;SearchBox opens the palette through the nearest ModalProvider; the slot components read the
active search from SearchProvider, which SearchPalette mounts around its children.
An Eloquent category
Section titled “An Eloquent category”EloquentSearchProvider covers the common case: give it the base query, the columns a term matches
against, and how a record becomes a result. Counting, paging and re-resolving a recorded selection
are the same for every such provider and come from the base class.
/** * @extends EloquentSearchProvider<Product> */#[AsSearchProvider('products', can: 'products.view')]final class ProductSearchProvider extends EloquentSearchProvider{ public function category(): SearchCategory { return new SearchCategory('products', __('Products'), 'package'); }
protected function query(): Builder { return Product::query()->orderBy('name'); }
protected function searchColumns(): array { return ['name', 'sku']; }
protected function result(Model $model): SearchResult { return SearchResult::make('products', new SearchResultItem($model->id, $model->name, route('products.show', $model))); }}query() is where scopes, ordering and the eager loads result() reads belong — it is called per
request, never memoized. An empty term matches everything, which is what the palette shows before
anything is typed; a % or _ in the term is escaped so it matches literally. Override
matching() for anything the column list cannot express, such as a relation or a full-text index.
Authorizing a provider
Section titled “Authorizing a provider”A provider is gated exactly like a definition: declare the ability on its attribute, and use
authorize() only for what can cannot express. A provider that fails is dropped from the
results, the category list and the counts, and its endpoint refuses a selection recorded against
it.
#[AsSearchProvider('products', can: 'products.view')]final class ProductSearchProvider implements SearchResultProvider { /* … */ }A provider has no sealed context of its own, so if the ability needs a subject the provider
supplies it — declare on and implement ResolvesGateSubject:
#[AsSearchProvider('products', can: 'products.view', on: 'workspace')]final class ProductSearchProvider implements ResolvesGateSubject, SearchResultProvider{ public function gateSubject(string $key): ?object { return $key === 'workspace' ? Workspace::current() : null; }}Declaring on without that contract throws at registration rather than silently denying every
request. A subject that resolves to nothing denies, the same as anywhere else in Lattice.
SearchResultProvider requires an authorize() because it is an interface and cannot default one.
When the declaration is the whole gate, take it from
Lattice\Core\Concerns\AuthorizesByDeclaration rather than writing return true by hand.
Translations
Section titled “Translations”The component’s strings ship with inline English defaults. With
laravel-i18next enabled, the plugin’s search
namespace is loaded automatically and serves the bundled en/de translations (override them
like any Laravel package translation — see Internationalization).
Bundle size
Section titled “Bundle size”The breakdown below measures the package’s built plugin — React and the framework runtime are external, so this is exactly what the search package adds on top of an app that already ships Lattice. It is regenerated on every docs build.
5.6 KB gzipped · 19.0 KB raw JavaScript
| Dependency | Raw | Gzip | Share |
|---|---|---|---|
| @lattice-php/search | 15.1 KB | 4.9 KB | 88.3% |
| Bundler runtime | 3.9 KB | 0.6 KB | 11.7% |
Emitted JavaScript files
| File | Raw | Gzip |
|---|---|---|
plugin.js | 19.0 KB | 5.6 KB |