inlst/blade-lucide-icons
A Laravel package for rendering Lucide icons in Blade templates. It provides Blade components and directives for the full Lucide icon set, with options for size, color, stroke width, and CSS classes, making icons easy to use consistently across your views.
The package is a thin Blade/Filament wrapper around the Lucide icon set: it registers a Blade component (and, in Filament, an icon set / plugin hook) so you can render any Lucide glyph by name without importing SVGs by hand. As of v1.40.0 the bundled Lucide snapshot was synced forward through twenty upstream releases (Lucide 1.29.0 → 1.49.0) in one drop — the PHP API did not change, but the icon catalogue did.
Minimal setup:
composer require inlst/blade-lucide-icons:^1.40
# Publish the config (tag may differ — check the README)
php artisan vendor:publish --tag=blade-lucide-icons-config
If it ships a Filament plugin, register it on the panel in app/Providers/Filament/AdminPanelProvider.php:
use Inlst\BladeLucideIcons\BladeLucideIconsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(BladeLucideIconsPlugin::make());
}
If it's Blade-only, the service provider is auto-discovered and the component is available immediately. Your first successful use case should be one nav icon and one inline icon:
{{-- Blade --}}
<x-lucide name="settings" class="w-5 h-5" />
{{-- Filament resource --}}
public static function getNavigationIcon(): string
{
return 'lucide-settings'; // confirm prefix from config
}
Where to look first, in order:
README.md — it is the only reliable source for the component name, config keys, and Filament registration signature for this specific fork.config/blade-lucide-icons.php after publishing — the prefix, path/disk, and default class/size keys control almost everything.src/ — usually 3–6 files. Look for the Blade::component(...) or Blade::componentNamespace(...) call in the service provider; that tells you the exact tag name to use.resources/views/ — if views are publishable, this is where the SVG wrapper and currentColor/stroke-width defaults live.resources/svg/ (or wherever the config points) — post-1.40 this directory is a much larger, much newer snapshot. If an icon vanished, this is where you confirm it.Do not start with a bulk migration of Heroicons. Render two icons, confirm they appear in a Filament panel and a plain Blade view, then scale.
1. Standardize on your own wrapper component. Given the package is a 1-star, 0-dependent project, the highest-leverage move is a one-file indirection so a future swap (to mallardduck/blade-lucide-icons, blade-ui-kit/blade-heroicons, or a raw SVG sprite) is a single edit:
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'size' => 20])
<x-lucide
:name="$name"
{{ $attributes->merge(['class' => 'shrink-0']) }}
width="{{ $size }}"
height="{{ $size }}"
aria-hidden="true"
/>
Then every view uses <x-icon name="user" /> and the underlying package tag never leaks into product code. This seam matters more after 1.40.0, not less — see the Gotchas on icon churn.
2. Filament nav/resource/action icons. The common day-to-day use is replacing emoji or Heroicon strings on resources, pages, and actions:
protected static ?string $navigationIcon = 'lucide-layout-dashboard';
// Actions
Action::make('export')->icon('lucide-download');
Keep a small constant map in a config or enum so icon strings aren't scattered — and so a post-upgrade rename is one edit, not a grep:
// config/ui.php
'icons' => [
'dashboard' => 'lucide-layout-dashboard',
'users' => 'lucide-users',
'billing' => 'lucide-credit-card',
],
3. Dynamic icons from data (with a whitelist). Rendering a database-driven icon is fine if you validate against a known set. This is the pattern that most often goes wrong (see Gotchas):
$allowed = ['lucide-home', 'lucide-star', 'lucide-bell'];
$icon = in_array($menu->icon, $allowed, true) ? $menu->icon : 'lucide-circle';
4. Pin and upgrade deliberately. v1.40.0 is a pure data refresh (20 Lucide releases at once), so the risk is not in PHP code you can review — it's in the SVG snapshot. Pin "inlst/blade-lucide-icons": "1.40.*", upgrade in a branch, and diff the rendered output rather than reading the changelog, which will only ever say "update Lucide".
5. Migration from Heroicons, incrementally. Do not regex-replace heroicon-o-* — the name sets differ. Run a small mapping table (heroicon-o-home → lucide-house, heroicon-o-cog → lucide-settings) and migrate surface-by-surface, starting with the navigation, because that's your highest-visibility, lowest-risk area.
6. Testing — now with an upgrade gate. Assert the component renders rather than asserting SVG bytes:
it('renders the dashboard icon', function () {
$this->blade('<x-icon name="lucide-home" />')
->assertSee('<svg', false)
->assertSee('lucide-home');
});
Add one smoke test per icon your app actually references (not just a sample), driven from the same config map you use in Filament. Since the wrappers typically fail soft — an unresolved name yields an empty <svg> rather than an exception — a passing test suite is the only cheap way to detect that 1.40.0 dropped or renamed a glyph you depended on. Better still, iterate the map:
it('resolves every configured icon', function () {
foreach (config('ui.icons') as $icon) {
$html = Blade::render('<x-icon name="'.$icon.'" />');
expect($html)->toContain('lucide');
}
});
7. Design-system consistency via CSS, not props. Let the SVG inherit color and size from the parent instead of passing size everywhere. A single rule like svg.lucide { width: 1em; height: 1em; stroke-width: 1.75; } keeps icons consistent across Filament tables, buttons, and Blade views.
src/ should need touching.home → house, alert-circle → circle-alert, edit-* → pen/square-pen, more-horizontal → ellipsis family). Whether your app breaks depends entirely on whether the wrapper resolves old names via aliases or only exact filenames. Run the config-map smoke test above against 1.40.0 before merging.github, twitter, facebook, chrome, …). If you were leaning on this package for social icons, 1.40.0 is the release where they stop rendering. Plan to vendor those few SVGs yourself.composer.json and treat upgrades as reviewed changes, not composer update noise.arrow-right); some wrappers accept PascalCase or add a lucide- prefix themselves. Passing lucide-lucide-arrow-right silently renders nothing or an empty <svg>. Check the provider's Blade::component call before assuming the prefix.<svg> means "not found", not "broken CSS". Most wrappers return an empty SVG when the name doesn't resolve. If an icon is invisible — especially right after this upgrade — first check that the file exists under the configured path, then look at Tailwind.fill vs stroke. Lucide is stroke-based (fill="none" stroke="currentColor"). If you inherit a Tailwind class that sets fill-current (common when swapping out a Heroicon solid), you'll get filled blobs. Reset with fill="none" on your wrapper.currentColor, not #000. Newer Lucide snapshots occasionally shift how attributes are emitted, so re-verify the published view after a data refresh like this one.heroicon-o-*, filament-*). If both the package and a panel plugin register an icon set, last-registered wins. If <x-icon> works but ->icon('lucide-...') doesn't, the Filament-facing registration is the problem — not the Blade component.php artisan config:clear and php artisan view:clear. If the package ships an icon-cache command, include it in your deploy script; if it doesn't, don't assume caching exists — and don't assume a cached manifest was invalidated by the version bump.name prop. Wrappers that do file_get_contents(base_path($path . $name . '.svg')) are trivially path-traversable (../../.env). Always whitelist dynamic icons server-side.aria-hidden="true"; meaningful ones need a <title> or adjacent visible text. The package wrapper almost certainly doesn't handle this for you.composer.json extra.laravel.providers; (2) does Blade::getClassComponentAliases() list the tag; (3) does the SVG file exist under the configured path (post-1.40, also check it wasn't renamed); (4) is the panel plugin registered after your theme's provider.prefix, default class/size — cheap to change), the publishable view (override once, keep the package's resolution logic), and your own <x-icon> wrapper (the real seam). Avoid forking the package; the wrapper gets you 90% of the flexibility with none of the maintenance.name, size, class) close to what the popular alternatives expose. If the package is abandoned — or a future bulk icon sync breaks a glyph you can't work around — swapping the one line inside components/icon.blade.php is a 10-minute change instead of a codebase-wide grep.How can I help you explore Laravel packages today?