digital-creative/icon-action-toolbar
A Filament package that turns table row actions into a compact icon action toolbar, helping Laravel admin panels save space while keeping common actions one click away. Configure icons, tooltips, and action groups for cleaner resource tables.
Resolve the framework question before you run anything. The registry metadata calls this a Filament package; the released code, changelog, and nova:publish hints say Laravel Nova. Those are not interchangeable — different provider base classes, different asset pipelines, different everything. Five minutes now saves a day:
composer show -a digital-creative/icon-action-toolbar # read the require: block
# after install:
cat vendor/digital-creative/icon-action-toolbar/composer.json
grep -r "Laravel\\\\Nova\\\\" vendor/digital-creative/icon-action-toolbar/src | head
If src/ imports Laravel\Nova\… and the provider extends Nova's tool provider, it is Nova. Write the answer down in your team's dependency notes so nobody re-litigates it.
Pin exactly. The package is 0.1.x with a structural refactor (the move onto the shared nova-packages-toolbar component, PR #16) landing in a patch release. You want every bump to be a deliberate diff you read first:
composer require digital-creative/icon-action-toolbar:0.1.3
Publish assets. This is the step people skip and then debug for an hour:
php artisan vendor:publish --provider="DigitalCreative\IconActionToolbar\ToolServiceProvider" --tag="assets"
# Nova projects usually just use:
php artisan nova:publish
Read the source in this order: README.md at the installed tag (not main) → src/ for the service provider, config, and the class you attach to a resource → resources/js/ read-only to learn the emitted DOM/class names (never fork) → the v0.1.3 notes, which listed hover states, table-row icon alignment, a scrollable detail toolbar, a replicate-icon fix, and the nova-packages-toolbar migration.
First use case: one dense index screen. Pick a triage resource — orders, moderation queue, support tickets — with three to five actions that already work as plain buttons. Convert only that resource. Keep the old button rendering reachable behind a config/flag so rollback is a one-line change rather than a revert commit. Success criterion is concrete: same actions, same authorization behaviour, same confirmation modals, fewer pixels per row, and a tab order you can walk end to end.
Opt in per resource, never globally. Density is only a win on scan-heavy screens; form-heavy or low-frequency resources should keep labelled buttons. A trait or static flag on the resource keeps this explicit:
class Order extends Resource
{
public static $iconToolbar = true; // confirm the real flag name in src/ for your tag
public function actions(Request $request): array
{
return [
NovaAction::make('view')->onlyOnIndex(),
NovaAction::make('edit')->onlyOnIndex(),
NovaAction::make('replicate')->onlyOnIndex(),
NovaAction::make('delete')->onlyOnIndex()->destructive()->requiresConfirmation(),
];
}
}
Pin the icon order and enforce it. Muscle memory is the UX payoff. Pick a house convention —
view → edit → replicate → custom actions → delete
— and put it in a shared abstract resource or trait so a new resource cannot drift. Someone reordering icons "for aesthetics" in a PR is a genuine regression class here, because the delete icon sliding one slot left is how accidents happen.
Compose custom actions in PHP; treat the toolbar as a renderer. Subclass the stock action, supply name(), icon(), and keep domain logic in handle():
class ApproveRefund extends Action
{
public function name(): string { return 'Approve Refund'; }
public function icon(): string { return 'check-circle'; } // match the icon set actually rendered
public function handle(ActionFields $fields, Collection $models): void
{
// domain logic lives here and nowhere else
}
}
If you find yourself passing closures into a Vue component or editing resources/js/, you have left the supported surface — and v0.1.3 is proof of how fast that surface moves.
Let authorization do the filtering — don't pre-filter in the toolbar. Define visible() / canSee() / policy authorization exactly as you would for a button, then let the framework remove it from the row. Verify with a restricted user that the remaining icons are correct and still in the pinned order; off-by-one alignment during permission filtering is the classic compressed-toolbar bug.
Keep destructive actions gated, not just iconised. Compressing Delete into an unlabelled glyph removes the label that was doing the safety work. Always pair it with requiresConfirmation() plus a clear modalHeading(), and keep it last in the row so it is never adjacent to the action the user is actually aiming at. If you cannot get a confirmation in place, leave that one action as a labelled button — mixed rendering is fine and is the safer default.
Handle replicate deliberately. v0.1.3 shipped a replicate-icon fix. Two consequences: remove any app-side workaround before upgrading (or you will get duplicate/missing buttons), and browser-test replicate specifically — it is the action most likely to look correct while copying nothing.
Test with a browser test, not a unit test. The failure modes here are visual and interaction-level:
$browser->loginAs($admin)->visit('/nova/resources/orders')
->waitFor('@icon-toolbar')
->assertElementsCount('@icon-toolbar .icon-action', 4) // use the real selector
->click('@icon-action-replicate')
->waitForText('Order copied');
Assert the negative too: log in as a user without delete permission, assert three icons render, and assert the third is your custom action rather than delete shifted left.
Treat the detail-page toolbar as a separate surface. v0.1.3 made it scrollable on overflow — that's a fallback, not a design. If a detail page routinely scrolls to reach actions, split into a primary toolbar plus an overflow menu, and make sure keyboard focus can enter and traverse the overflow region.
Assets are cached and will lie to you. Every v0.1.3 change was frontend-only, so if the icons look identical after composer update you are probably looking at stale compiled assets. Run php artisan nova:publish (or the package's asset tag), then php artisan optimize:clear, hard-refresh, and confirm the new JS hash in the network tab. Budget this into every bump — it is the most common false "it didn't work" report.
Your CSS overrides are the most fragile thing you own. PR #16 moved the toolbar onto the shared nova-packages-toolbar component; class names almost certainly moved. Assume anything targeting the old DOM (spacing, hover colour, dark-mode borders) is broken and re-verify in both light and dark. Keep overrides in one small, commented stylesheet so the next bump is a five-minute diff — or better, skip overrides and use the package's own styling options.
Icon-only buttons silently destroy accessibility. A custom action with no label has no accessible name; a screen reader user cannot discover or trigger it. Every custom icon action needs a real label plus a tooltip that is not the only source of the name. Do a keyboard pass: tab into the toolbar, tab through every icon in order, confirm the focus ring is visible against your panel background in both themes, and confirm tooltips fire on focus, not just hover.
Do not fork the Vue components. The internals changed materially in a patch release. Anything you maintain under resources/js/ will conflict on the next bump. If you need behaviour the package doesn't offer, subclass the PHP action or accept the labelled-button fallback for that one action.
Version and maturity discipline. 0.1.x, one maintainer, a first-time contributor landing a structural refactor, zero dependents. Concretely: pin exactly, treat each bump as a change request with a documented rollback pin, never put this on the critical path of an operational workflow without a one-line fallback, and read the changelog before composer update.
Compatibility is undocumented — verify it yourself. There is no stated support matrix for Nova/Laravel/PHP. On a scratch branch, confirm the Nova major against the package constraint, and check whether your app compiles its own Nova assets via Nova::script() / Nova::style(). If it does, the package assets may need adding to your build explicitly — forgetting that gives you an unstyled toolbar.
Long action lists degrade quietly. Five icons beat five buttons; nine icons are slower than a dropdown. Cap the toolbar at roughly four to six actions and promote the rest into an overflow menu. The scrollable detail toolbar from v0.1.3 is a signal the maintainer hit this wall too.
Debugging checklist when icons are missing or wrong, in order:
composer show and bootstrap/cache/packages.php; auto-discovery fails silently when composer.lock and the cache disagree — run composer dump-autoload and php artisan package:discover.public/vendor/… against the vendor directory.Extension points, ranked by stability: action icon() / visible() on PHP classes (stable) → ordering in actions() (stable) → package config file (mostly stable) → published CSS overrides (fragile) → resources/js/ internals (do not touch). Weight your investment to the left.
How can I help you explore Laravel packages today?