nothingworks/blade-svg
A Laravel package for inlining SVG files directly in Blade templates. Use the @svg directive to embed icons and graphics with custom attributes and classes, plus caching for performance. Manage and style SVGs with CSS without extra HTTP requests.
Install the package:
composer require nothingworks/blade-svg
The public API is identical to the upstream blade-ui-kit/blade-icons, so upstream docs and examples apply verbatim.
(Optional) Publish the config if you need custom sets or a fallback icon:
php artisan vendor:publish --tag=blade-icons
Create a directory for your SVGs (e.g. resources/svg/) and register it in config/blade-icons.php:
return [
'sets' => [
'default' => [
'path' => 'resources/svg',
],
],
'fallback' => null,
];
Drop an SVG file named home.svg into that folder and render it in any Blade view:
<x-icon-home class="w-6 h-6 text-gray-500" />
Or via the directive:
@svg('home', 'w-6 h-6 text-gray-500')
First use case: replacing <img src="..."> icon assets in navigation, buttons, and empty states. Because the SVG is inlined, it inherits currentColor, so a single Tailwind text-* class controls the icon color and hover states work for free.
First place to look when something breaks: config/blade-icons.php (sets, paths, prefixes) and php artisan icons:cache / icons:clear. These two commands explain 90% of "my icon doesn't exist" reports.
Multiple icon sets with prefixes. Register vendor packs alongside your own icons and give each a prefix:
'sets' => [
'default' => ['path' => 'resources/svg'],
'heroicons' => [
'path' => 'vendor/blade-ui-kit/blade-heroicons/resources/svg',
'prefix' => 'heroicon',
],
'lucide' => [
'path' => 'vendor/lucide-icons/resources',
'prefix' => 'lucide',
],
],
Then <x-heroicon-o-home />, <x-lucide-search />, <x-icon-home /> all coexist. The default set renders under the icon prefix.
Dynamic icon names. The component name is resolved at parse time, so you can't interpolate inside the tag. Use the directive/helper instead:
@svg("heroicon-o-{$status}", 'w-5 h-5')
{{ svg('heroicon-o-'.$status)->class('w-5 h-5') }}
svg() returns a fluent wrapper — ->class(), ->width(24), ->height(24), or ->toHtml() — which is handy inside PHP/controllers, mailables, and Blade components with logic.
Wrapping with project defaults. Almost every app ends up with a project component that hard-codes size and accessibility:
{{-- resources/views/components/app-icon.blade.php --}}
@props(['name', 'size' => 'w-5 h-5'])
{{ svg($name)->class($size)->attributes($attributes) }}
This gives one place to enforce aria-hidden="true" for decorative icons and to switch icon packs later.
Passing arbitrary attributes. Any attribute on <x-icon-…> is forwarded to the root <svg> element, so class, id, aria-label, x-on:click, and x-bind all work naturally:
<x-icon-cog class="w-5 h-5 animate-spin" aria-hidden="true" />
Production caching. In deploy.sh, after composer install and before php artisan view:cache:
php artisan icons:cache
This writes a manifest to bootstrap/cache/blade-icons.php and turns directory scanning into a single array lookup. Run php artisan icons:clear locally whenever you add a file — otherwise you'll debug a "missing" icon that is actually just a stale manifest.
Livewire / Alpine integration. Since icons are inlined, Alpine's x-show, x-bind:class, and Livewire's DOM diffing treat them like any other element. Avoid putting an icon inside a wire:key-less loop where its id would be duplicated; if you need unique IDs (e.g. inside <defs>), generate them with Str::uuid() and pass them explicitly.
Fallback icons. Set 'fallback' => 'heroicon-o-question-mark-circle' so that a missing icon renders something in production instead of throwing — very useful for CMS-driven icon names.
icons:cache does not watch the filesystem. Any new SVG added after the manifest is built will 404 until you re-run it. Add icons:clear to your local dev setup or skip caching in local.arrows/left.svg → arrow-left in some setups), and underscores/camelCase are converted to kebab-case. Stick to lowercase-kebab filenames and you'll never have to think about it.icon), the later one in the config wins silently. Always set an explicit prefix for third-party packs.@svg vs <x-icon-…> resolution differ. The directive takes the full dotted/--joined name (including the set prefix), while the component uses the Blade x-{prefix}-{name} convention. Mixing them up produces "unable to locate icon" errors that look identical but aren't.<svg> root; a class you pass is appended, not replaced, so utility conflicts (w-4 vs your default w-6) follow Tailwind's stylesheet order, not the passed order. Enforce one size via a wrapper component if this bites you.fill="#000" won't respect currentColor; strip fill and add fill="currentColor" during export or via a small pre-commit check.{{ }}, no @if — template variables must be resolved before passing the icon name.Home.svg works on macOS and breaks on your Linux CI/production image. Normalize filenames in review.aria-hidden or role. Decorative icons should get aria-hidden="true"; meaningful ones need role="img" plus an aria-label or a <title> inside the SVG.<x-icon-…> is an inline <svg>. A table with 200 rows × 3 icons produces 600 inline SVGs — fine for DOM size, brutal for HTML payload. For repeated icons in large lists, prefer a cached sprite sheet and <use> references.config:cache before deploy, edits to config/blade-icons.php won't take effect until you re-run php artisan config:clear. This matters for teams that tweak prefixes per environment (e.g. different icon packs in white-label builds).BladeUI\Icons\Factory bindings or a custom Svg subclass if you need to post-process markup (e.g. add focusable="false"). Register it in a service provider after the package's own bindings.nothingworks/blade-svg) tracks the upstream blade-ui-kit/blade-icons repository. Before adopting, confirm the fork's release cadence matches upstream — the "last release" date here (2026-06-30) is far ahead of the version most projects pin, so composer require may pull a version whose API diverges from the docs you're reading. Pin a known-good tag in composer.json and diff against upstream release notes on upgrade.How can I help you explore Laravel packages today?