lara-zeus/popover
lara-zeus/popover adds a lightweight popover component for Laravel and Filament apps, letting you attach rich HTML or Livewire content to buttons and links with a few lines of Blade. Configure placement, width, and trigger behavior while keeping markup clean, ideal for inline help, quick actions, an...
Install, then immediately verify the Filament minor you are pairing it with.
composer require lara-zeus/popover
There is nothing else to do: no vendor:publish, no migrations, no service provider registration. That is the entire install and it is the package's main selling point.
Before you write a line of integration code, run these side by side:
composer show lara-zeus/popover
composer show filament/filament
The package extends Filament's table column and infolist entry classes, so it inherits Filament's internal API surface. Filament minors move; a 31-star package with zero dependents has no downstream canary to catch a break for you. Confirm the tag you installed resolves against the Filament version you actually run, then pin both in composer.json (see Gotchas).
Where to look first, in this order:
src/ — the column and entry classes. Read the actual method signatures for content(), trigger behaviour, and placement. Do not trust the README over the source; with no dependents reporting issues, the source is the spec.resources/views/ — the wrapper Blade view. This tells you which CSS classes and Alpine directives you inherit, and — critically — whether content() output is escaped or emitted raw. That one detail determines how much review your partials need.PopoverColumn (tables) and PopoverEntry (infolists).First use case. Pick exactly one read-only, non-sensitive column on a staging panel. A relation count, a "last updated by" summary, or a status explanation is ideal — you exercise the whole path (closure → Blade partial → rendered popover) without touching anything load-bearing.
use LaraZeus\Popover\Tables\Columns\PopoverColumn;
PopoverColumn::make('notes')
->content(fn ($record) => view('admin.popovers.notes', ['record' => $record]))
->label('Notes');
Create resources/views/admin/popovers/notes.blade.php, render the table, hover the cell. If the partial's content appears, the integration is done — everything after that is content authoring and scoping discipline, not package work.
Return a Blade partial from content(), never an inline HTML string. Inline strings inside a resource class are the fastest way to produce an unreviewable, unescapable, untestable mess. A named partial gives you a reviewable file, a render path you can test in isolation, and a one-file fix when the package's wrapper view changes.
PopoverColumn::make('customer')
->content(fn ($record) => view('admin.popovers.customer-summary', [
'record' => $record,
'orders' => $record->orders()->latest()->limit(5)->get(),
]))
->label('Customer');
Keep the closure dumb. The closure runs per row; anything beyond view(...) with a pre-resolved view model belongs somewhere else. Compute formatting, permissions, and derived data in a small view model or in the partial itself, so the logic is unit-testable outside Filament. A closure with three ternaries in it is a closure you cannot test and will not want to touch in six months.
Wrap the call site behind your own helper — this is the highest-leverage pattern for this package. Every popover across every resource should be constructed in one place:
namespace App\Filament\Support;
use LaraZeus\Popover\Tables\Columns\PopoverColumn;
final class Popover
{
public static function column(string $name, string $view): PopoverColumn
{
return PopoverColumn::make($name)
->content(fn ($record) => view($view, ['record' => $record]));
}
}
Resources call Popover::column('notes', 'admin.popovers.notes'). When the package lags a Filament minor, or when you decide to swap to a native modal action or tooltip, you edit one file. Without this wrapper, a break is a sweep across every resource class in the panel — exactly the failure mode the adoption numbers make plausible.
Deliberately mirror the table and infolist patterns, but don't share partials between them on the first pass. Tables get a PopoverColumn; infolists get a PopoverEntry. The record context is identical, but the layout constraints are not: a table cell is narrow and clipped, an infolist entry is not. Prove each independently with its own partial, then factor out the genuinely shared content.
Use infolists for the richer content and tables for the one-liners. A useful split: table popovers answer a single question ("why is this row red?"); infolist popovers carry the five-row detail that used to justify a modal round-trip or a full record page. That split is what actually reduces clicks — it is not about using every feature the package ships.
Eager-load whatever the closure touches. A popover column that lazy-loads a relation fires one query per row, indistinguishable from any other column callback and invisible on a 10-row staging table:
public static function table(Table $table): Table
{
return $table
->modifyQueryUsing(fn ($query) => $query->withCount('orders')->with('latestOrder.user'))
// ...
}
Track every popover surface in a small registry. A markdown table or config array listing column, resource, partial, owner, and purpose. Each popover is a content surface that goes stale the moment the data model moves, and nothing upstream will warn you. The registry is the cheapest insurance available for a dependency with no community watching it.
Pin the version and add a checkpoint to your Filament upgrade runbook. "lara-zeus/popover": "3.0.2" — not ^3.0. Add "verify popovers render" to the upgrade checklist; it is a two-minute check at the exact moment you are most likely to break it.
Test at the Livewire level, not visually. A table test asserting the column exists and the partial renders catches upgrade breakage cheaply and runs in CI:
livewire(ListOrders::class)
->assertTableColumnExists('notes')
->assertSee('Customer summary');
Pair that with a plain Blade test that renders the partial directly with a factory record. Together they isolate package breakage from content breakage when something goes wrong, which is the difference between a five-minute diagnosis and an afternoon.
Determine the escaping path from source before rendering any user-derived data. Whether content() output flows through Filament's schema system (inheriting escaping and policy behaviour) or is emitted raw is the single biggest review variable in this package, and the docs are too thin to answer it definitively. Open the wrapper Blade view and look for {!! !!}. If it is there, every partial you author must escape explicitly: {{ }} for all model attributes, e() on anything concatenated, and no {!! $record->notes !!} under any circumstances. Decide this before the first popover ships, not after.
Table cells clip, and popover placement inherits the row's containing block. Popovers anchored inside a table row stack against the row's overflow and z-index context. Test against your real table, not a five-row fixture: a popover in the last column, a popover near the bottom of a scrollable table, a popover inside a table inside a relation manager. If content gets cut off, moving that surface to an infolist entry is almost always cheaper than fighting the CSS.
Accessibility and mobile behaviour are unverified. The package carries no stated accessibility policy. Before shipping anything user-facing, check: can you trigger it without a mouse, does Escape close it, does focus move into the panel and back out, does a screen reader announce it, does it fit a phone viewport. If any of those fail and the surface is customer-facing, do not ship there — scope it to internal admin screens where a native tooltip or modal action is an acceptable fallback.
Cap the density at two or three popovers per view. Ten popover-bearing columns turns an aid into clutter, makes the screen slower to scan, and multiplies your maintenance surface. The intent is fewer page transitions, not more things to hover.
Keep the content a summary, not a mini-application. No embedded tables with their own pagination, no charts, no full-size images. Heavy content makes the popover feel broken on hover and creates a second UI to maintain — at which point you should have built the infolist entry or the view page.
Debugging is Blade debugging, in two steps. If a popover renders blank: (1) render the partial in isolation — view('admin.popovers.notes', ['record' => $record]) in tinker or a test; (2) check whether the closure is receiving the record at all. The 3.0.2 release note "fix get state" indicates state resolution has been a moving target, so suspect the closure argument before suspecting your Blade. If it renders but is mispositioned, that is a CSS containing-block problem, not a package bug — see clipping above.
Agree the fallback before you ship the first one. Know what replaces it: a native Filament modal action, an infolist entry, a tooltip, or a dedicated view page. Because every call site goes through App\Filament\Support\Popover, the swap is a one-file change — but only if you actually used the wrapper everywhere. One resource that imported PopoverColumn directly is the one that will bite you.
Treat the release date as a maintenance signal, not a feature signal. The 3.0.2 changelog is documentation reorganisation plus a bug fix. That is a healthy maintenance signal for a small package, but it also means you should not expect new capability to arrive and should not plan features that depend on upstream additions. If you need behaviour the package lacks, vendor or fork it — at this size the source is short enough to own.
How can I help you explore Laravel packages today?