blade-ui-kit/blade-icons
Blade Icons lets you drop SVG icons into Laravel Blade views with a simple component or directive, like or @svg('camera', 'w-6 h-6'). It's a lightweight base package with many third-party icon set packages available.
Install the base package, then publish and uncomment the configuration — this is the single most common reason a fresh install "does nothing":
composer require blade-ui-kit/blade-icons
php artisan vendor:publish --tag=blade-icons
In config/blade-icons.php, uncomment the default set and point it at a directory that exists:
'sets' => [
'default' => [
'path' => 'resources/svg', // relative to base_path(), resource_path() also works
],
],
Drop any *.svg file (a single root <svg> element, no XML declaration) into resources/svg, and you immediately get two APIs for free:
<x-icon-camera class="w-6 h-6" /> {{-- component, prefix "icon" for the default set --}}
@svg('camera', 'w-6 h-6') {{-- directive --}}
@svg('camera', 'w-6 h-6', ['fill' => 'none', 'data-testid' => 'camera'])
Where to look first: config/blade-icons.php (set definitions, prefixes, defaults, cache), resources/svg (your own set), and the icon search at https://blade-ui-kit.com/blade-icons#search before you hand-draw anything. Prefer an existing icon package (Blade Heroicons, Blade Lucide, Blade Font Awesome, etc.) over committing hundreds of SVGs — they register their own set and prefix via config.
First real use case: take the inline <svg> blobs already scattered through your layouts/ and button partials, extract them to resources/svg, and replace each call site with <x-icon-…>. You get one source of truth per glyph, class passthrough for Tailwind sizing/colour, and greppable usage.
Component vs. directive vs. helper. Use <x-icon-camera /> for static, known icons in Blade templates — it's discoverable and IDE-friendly. Use @svg($name) when the icon name is data-driven (a menu item row with an icon column, a status badge mapping). When you need the markup outside a Blade rendering context (a JSON payload, a notification array, a PDF builder), use the svg() helper, which returns a BladeUI\Icons\Svg object implementing Htmlable:
$icon = svg('camera', 'w-6 h-6');
return ['html' => $icon->toHtml()];
Multi-set config with prefixes. The default set uses the icon prefix; other sets default to their set name unless you set prefix. Give each library a stable, short prefix and its own directory:
'sets' => [
'default' => ['path' => 'resources/svg'],
'heroicon' => [
'path' => 'vendor/blade-ui-kit/blade-heroicons/resources/svg',
'prefix' => 'heroicon',
'class' => 'w-5 h-5',
'attributes' => ['fill' => 'none', 'stroke' => 'currentColor'],
],
],
Set-level class and attributes act as defaults for every icon in that set — the natural place to put aria-hidden="true", fill="none", or a baseline size so templates stay terse.
Wrap it in your own component. Don't call <x-icon-*> directly from every view; build a design-system wrapper so sizing, accessibility, and variant mapping live in one place:
{{-- resources/views/components/ui/icon.blade.php --}}
@props(['name', 'size' => 'md'])
@php
$sizes = ['sm' => 'w-4 h-4', 'md' => 'w-5 h-5', 'lg' => 'w-6 h-6'];
$set = str_contains($name, '.') ? $name : "default.$name";
@endphp
<x-dynamic-component
:component="'icon-' . str_replace('.', '-', $name)"
{{ $attributes->merge(['class' => $sizes[$size]])->merge(['aria-hidden' => 'true']) }}
/>
Or back it with an enum/registry class (Icon::Camera->value) so a rename in the icon set is a one-line change rather than a global search-and-replace.
Runtime names. Blade component names can't be interpolated directly — use <x-dynamic-component :component="'icon-'.$icon" />, @svg($icon), or the wrapper above. Validate $icon against an allow-list; never pass raw request input into @svg (it resolves names to filesystem paths and outputs raw markup).
Deploy and cache. The package builds an icon manifest; in production cache it as part of your deploy script so you don't hit the filesystem for every render:
php artisan icons:cache
Run it after composer install so vendor-provided icon set paths exist, and pair it with icons:clear locally when adding icons. Add php artisan icons:clear to any "new icon added" dev workflow, or simply disable the cache in local.
Non-HTML output. Email templates strip <style> and often class-based styling, so set explicit fill/stroke/width/height attributes on the icon rather than relying on Tailwind classes. The same applies to DomPDF/Snappy renders: pass concrete attributes via the third @svg argument.
Testing. Assert rendered output with assertSee('camera', false) on the view, or assert the directive/component throws BladeUI\Icons\Exceptions\SvgNotFound for a bad name. A simple test that iterates your icon enum and renders each one catches renamed/removed files before a user does.
default set is the #1 failure mode. Installed, published, no icons found → check config/blade-icons.php first, then verify the directory actually exists. A set registered with a non-existent path fails silently until render time.icons:clear (and icons:cache in prod). Also remember php artisan config:cache after editing set definitions.SvgNotFound in production surfaces as a 500 inside a loop (e.g. a table row with a missing icon in the DB). Wrap user-facing dynamic lookups in a try/catch and fall back to a known-good icon, or configure a fallback set so a missing icon resolves elsewhere instead of throwing.<svg>, no <?xml ?> prolog, no embedded <script> or event handlers, and keep viewBox. Icons authored at inconsistent viewBox sizes will look misaligned even with identical classes.<svg> and set-level defaults are applied underneath; class precedence and boolean attributes (fill="none" vs a set default) are worth a two-minute check in vendor/ when something refuses to override. If you fight it repeatedly, move defaults out of the set config and into your own wrapper.currentColor. If an icon won't respond to text-red-500, the source SVG has a hard-coded fill="#000" or stroke="#000". Normalise to currentColor when importing a set.<use> instead of inline duplication.aria-hidden="true" (and focusable="false" for legacy Edge); meaningful icons need role="img" plus an aria-label or an embedded <title>. Bake this into the wrapper component so it's never forgotten.ArrowUp.svg ≠ arrow-up.svg on Linux CI, fine on macOS). Standardise on lowercase kebab-case. Nested subdirectories exist in the wild but directive and component resolution treat them differently across versions — verify before reorganising an icon set into folders.vendor/…/resources/svg breaks if a package restructures or you use a custom vendor-dir (COMPOSER_VENDOR_DIR). Prefer first-party icon packages that publish correct config, and re-run icons:cache after every composer update.BladeUI\Icons\Factory (bound as a singleton) to add set resolution logic programmatically, use Factory::svg() to fetch an Svg instance server-side, and register your own Blade directive/component wrapper if you want per-project conventions like @ui-icon('camera', 'lg'). Registering sets from a service provider is also a clean way to make a private in-house icon library available without touching config/blade-icons.php.How can I help you explore Laravel packages today?