fuzzyfox/lucide-for-laravel
A Laravel package that brings the Lucide icon set to your Blade views, offering simple components and helpers to render crisp, customizable SVG icons anywhere in your application with minimal setup.
Install / upgrade
composer require fuzzyfox/lucide-for-laravel # fresh install
composer update fuzzyfox/lucide-for-laravel # v2.3.0 → v2.4.0 (drop-in)
php artisan view:clear
php artisan icons:cache # blade-icons sets are cached; re-run if you cache icons
There is nothing to publish or configure for the common case — icon registration is automatic. Where to look first: the Lucide enum shipped in the package (search enum Lucide under vendor/). It is the authoritative, IDE-autocompletable list of every icon this version supports, and it is what you should code against rather than raw strings. After that, the release table (slug ↔ enum case ↔ Blade name) is the fastest reference for the 32 new icons.
First use case: render an icon in Blade with the enum, not a literal string.
@svg(\Fuzzyfox\Lucide\Lucide::ArmenianDram->value, 'size-5 text-gray-500', ['aria-hidden' => 'true'])
If your version wires the enum straight into the Blade tag, @svg(Lucide::Cupcake) may work too — check the enum's accessor once and then standardise on one form across the codebase.
What this release actually changes for you: v2.4.0 is purely additive — 32 new glyphs plus matching enum cases, 8 existing glyphs redrawn, zero renames, zero removals. Your upgrade risk is visual, not structural.
Wrap the directive in one Blade component. The single highest-leverage move with an icon package is to never call @svg from feature views. Create resources/views/components/icon.blade.php that takes a Lucide case, applies your default size/stroke, and sets aria-hidden unless a title is passed. Every future icon-pack upgrade then lands in one file.
{{-- <x-icon :name="Lucide::HouseCog" class="size-4" /> --}}
@props(['name', 'title' => null])
@svg(
$name->value,
$attributes->merge(['class' => 'size-5 shrink-0']),
$title ? ['role' => 'img'] : ['aria-hidden' => 'true']
)
Store the enum, not the string, in config and nav arrays. Menus, settings tabs, and status maps read best as ['icon' => Lucide::DoorClosedCog, 'label' => 'Permissions']. It gives you static analysis, refactor safety, and a single place to swap a glyph later.
Validate dynamic icon names with tryFrom. For admin icon pickers (a genuinely nice use of Lucide::cases()), never interpolate untrusted input into @svg("lucide-{$name}"):
$icon = Lucide::tryFrom($request->string('icon')->toString()) ?? Lucide::Circle;
Build the picker by iterating Lucide::cases() and deriving labels with Str::headline($case->name). Sort explicitly — cases() reflects declaration order, which is alphabetical-by-slug today but is not a contract.
Add a Pest smoke test that every case resolves. This is the test that pays for itself on every icon bump, because it catches a missing/renamed glyph at CI time rather than in production:
it('renders every lucide icon', function () {
foreach (Lucide::cases() as $case) {
expect(blade("@svg('{$case->value}')"))->toContain('<svg');
}
});
Favour the new domain sets over generic placeholders. This release fills real gaps: currency glyphs (armenian-dram, bangladeshi-taka, kazakh-tenge, nepali-rupee) for pricing/locale switchers, house-cog / door-closed-cog / door-closed-package for property-and-permissions UIs, clef-* for anything audio, and a coherent line-dot-* / text-align-justify-* group for editor toolbars. Using the specific glyph beats a generic settings icon and costs nothing.
Treat lucide as stroke-first. Size with size-*/w-*, colour with text-* (the SVG uses currentColor), and tune weight with a stroke-width attribute rather than CSS fill. Keep your wrapper's defaults so icons stay visually consistent across the app.
Digits are spelled out in enum cases. printer-3d is Lucide::PrinterThreed, not Lucide::Printer3d. PHP enum cases cannot start with a digit and the generator strips/expands numeric segments. If you generate case names from slugs yourself (e.g. in a seeder or import script), replicate this rule or you will silently miss icons.
"Purely additive" covers names, not pixels. Eight glyphs were redrawn upstream: broccoli, calendar-chevrons-right, card-sim, mail-pen, map-pinned, nut, nut-off, save-off. Nothing breaks, but if you run visual/screenshot regression tests (Chromatic, Percy, Playwright snapshots) expect diffs on those eight and review rather than blindly re-baseline — a redraw can subtly change optical weight next to your text.
lucide-static is not yours to bump. The ^1.x pin lives inside the package's build pipeline (ADR-0009: bump → sync → test → PR). If an icon you saw on lucide.dev is missing here, the answer is "wait for the next sync" — not npm install. The workaround for a genuinely urgent icon is to register your own SVG set alongside this one (a documented blade-icons extension point) and only use the fallback until the package catches up.
Clear icon and view caches after upgrading. New icons exist as files, but cached manifests and compiled views will not know about them. Symptom: @svg('lucide-cupcake') renders nothing or throws "icon not found" on a machine that just ran composer update. Fix: php artisan icons:clear && php artisan icons:cache plus view:clear.
Don't persist enum names as long-lived user data without a fallback. Storing Lucide::ArmenianDram->value in a settings table is fine, but read it back through Lucide::tryFrom(...) ?? default. Upstream lucide does occasionally rename slugs; this release promises none, and the package's automated pipeline makes those events rarer — but a ValueError on a settings page is a bad way to find out.
Accessibility is opt-in. Decorative icons need aria-hidden="true"; meaningful icons (icon-only buttons, status indicators) need role="img" plus a <title> or adjacent visually-hidden text. Doing this in your wrapper component is far cheaper than auditing call sites later.
Dynamic directive calls defeat static analysis. @svg("lucide-{$foo}") works at runtime but no tool can tell you whether $foo is valid. Prefer passing the enum into your component and letting the wrapper do ->value; reserve string interpolation for the admin-picker path guarded by tryFrom.
Version pinning in CI. ^2.4 in composer.json will not pull a new icon set until something actually runs composer update. If your CI caches vendor/ aggressively, teammates will see the 32 new icons and CI won't — pin and bump deliberately, or run the smoke test above so a stale vendor tree fails loudly.
How can I help you explore Laravel packages today?