codeat3/blade-emblemicons
Blade Emblemicons offers lightweight Laravel Blade components for rendering Emblemicons icons directly in your views. Install via Composer and use simple x-emblemicon tags with customizable classes and attributes to add crisp SVG icons to any Laravel app.
Install. One command, no publishing required:
composer require codeat3/blade-emblemicons:^1.6
Service-provider and Blade-component auto-discovery mean there is nothing to register. Publish config only if you need to change defaults:
php artisan vendor:publish --tag=blade-emblemicons-config
Where to look first — not the README, the filesystem. The authoritative source of truth for icon names is the vendored SVG directory. Open it before writing a single tag:
ls vendor/codeat3/blade-emblemicons/resources/svg/ | head -50
ls vendor/codeat3/blade-emblemicons/resources/svg/ | wc -l # know the size of the set
Every icon is a filename; the component name is that filename minus .svg. There is no generated manifest or enum to autocomplete against, so this directory is your API contract. If your editor can't autocomplete icon names, that's expected — plan for it (see Gotchas).
Confirm the prefix once, then forget it. This package registers icons under the emblemicons prefix, which means the component tag is x-{prefix}-{icon}:
<x-emblemicons-home />
<x-emblemicons-arrow-right class="w-5 h-5 text-gray-500" />
{{-- directive form, identical output --}}
@svg('emblemicons-home', 'w-5 h-5 text-gray-500')
The repo description shows <x-emblemicon-name /> (singular) in one spot and <x-emblemicons-{name} /> elsewhere. Verify empirically on install — render one icon in a scratch view and check the rendered HTML, or read the published config's prefix key:
// config/blade-emblemicons.php
return [
'path' => 'vendor/codeat3/blade-emblemicons/resources/svg',
'prefix' => 'emblemicons',
'disk' => null,
];
First real use case. Pick the lowest-stakes surface you own — a settings page, an admin nav, a 404 — and convert three icons: one navigation glyph, one status/action glyph, one icon that needs a semantic color. This exercises the three things that actually break day-to-day: name spelling, sizing, and currentColor inheritance.
<nav>
<a href="{{ route('dashboard') }}" class="flex items-center gap-2">
<x-emblemicons-home class="w-5 h-5 text-slate-500" />
Dashboard
</a>
</nav>
If text-slate-500 recolors the icon, you're done — the SVG inherits currentColor and Tailwind utility sizing works. If it doesn't, stop and fix that before migrating any more views, because every screen you build depends on it.
Decide on a sizing convention before your second screen. The most common failure mode with any Blade icon package isn't rendering — it's a codebase with w-4 h-4 in the header, w-5 h-5 in the sidebar, size-6 in a card added three months later, and a stray inline style="width:18px". Pick one axis of control and standardize:
{{-- Pattern A: utility classes at the call site (most common, most flexible) --}}
<x-emblemicons-user class="size-5 shrink-0" />
{{-- Pattern B: named Blade wrapper enforcing your tokens --}}
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'size' => 'md'])
@php
$sizes = ['sm' => 'size-4', 'md' => 'size-5', 'lg' => 'size-6', 'xl' => 'size-8'];
@endphp
<x-dynamic-component :component="'emblemicons-' . $name" {{ $attributes->class([$sizes[$size], 'shrink-0']) }} />
Pattern B is what you want if more than one developer touches the front end, because it gives you one place to change sizing and one place to add aria-hidden defaults. It also gives you a grep target: <x-icon name="..." is auditable; raw <x-emblemicons-* /> scattered across 200 files is not.
Wrap in a named-component layer if you're already on a component-based design system. The package gives you primitive icons; your app should give you semantic ones. This is the difference between a design change being a one-file edit and a find-and-replace:
{{-- resources/views/components/status-badge.blade.php --}}
@props(['status'])
@php
$map = [
'active' => ['icon' => 'check-circle', 'class' => 'text-emerald-600'],
'pending' => ['icon' => 'clock', 'class' => 'text-amber-600'],
'failed' => ['icon' => 'x-circle', 'class' => 'text-rose-600'],
][$status] ?? ['icon' => 'question-mark-circle', 'class' => 'text-slate-400'];
@endphp
<span {{ $attributes->class(['inline-flex items-center gap-1.5', $map['class']]) }}>
<x-emblemicons :name="$map['icon']" class="size-4" />
{{ $slot }}
</span>
Note this relies on your x-icon/x-emblemicons wrapper from Pattern B, not the package directly — that indirection is the whole point. When the day comes to swap icon sets (or fork them into resources/svg), you edit one component.
Keep an icon-name constants file. Icon names have no stable API contract upstream — a minor bump can rename or remove one, and Blade will throw Unable to locate a class or view for component at render time, which in a lazy-loaded partial means a 500 in production on a page nobody clicked in QA. Enumerate what you actually use:
// app/Support/Icons.php
final class Icons
{
public const NAV = [
'home' => 'home',
'settings' => 'settings',
'billing' => 'credit-card',
'team' => 'users',
];
public const STATUS = [
'ok' => 'check-circle',
'warn' => 'alert-triangle',
'err' => 'x-circle',
];
}
This converts "did upstream rename this?" from a runtime question into a review-time question, and it makes a smoke test trivial to write (see below).
Use @svg for cases where a component tag is inconvenient. The directive form is the same set, same attributes, but usable where components are awkward — inside a @foreach producing raw markup, in a mail template built as a string, or in a config-driven menu array:
@foreach (config('nav.items') as $item)
<a href="{{ $item['url'] }}">
@svg('emblemicons-' . $item['icon'], 'size-5')
{{ $item['label'] }}
</a>
@endforeach
For email templates, know that inline SVG support is patchy — Outlook desktop in particular does not render it. Blade icon packages produce <svg>; if your mail surface includes Outlook, plan for a PNG fallback or skip icons there. Don't discover this after building the receipt email.
Livewire is a non-issue, but only if you keep it static. Icons are pure server-rendered markup, so they cost nothing on re-render — but avoid driving the icon name from a polled public property if you can render it with @svg inside the static part of the template. In practice: <x-emblemicons-{{ $dynamicName }} /> does work via x-dynamic-component, but it defeats any compile-time validation and makes the constants file meaningless for that usage. Prefer mapping a state enum to a constant name.
Add a smoke test at adoption time. This is the single highest-value thing you can do given the abandonment/rename risk profile. Render every icon name in your constants file and assert no exception:
it('renders every registered icon', function () {
foreach (array_merge(Icons::NAV, Icons::STATUS) as $name) {
$this->view('icons-test', ['name' => $name])
->assertSee('<svg', escape: false);
}
});
A future minor that drops an icon then fails CI instead of production. Cheap, and it's the guardrail that makes an unmaintained-but-thin package safe to keep.
Integration tip — attribute passthrough is inherited, not defined here. class, width, height, aria-*, fill behaviour comes from blade-ui-kit/blade-icons, not this package. Check the resolved version in your lockfile when debugging:
composer show blade-ui-kit/blade-icons
Accessibility convention, decided once. Decorative icons next to a text label should be aria-hidden="true"; standalone icons in an icon-only button need aria-label on the button, not the SVG. Bake whichever applies into your wrapper's default so developers don't have to remember:
{{-- icon.blade.php, decorative by default --}}
<x-dynamic-component :component="'emblemicons-' . $name" aria-hidden="true" {{ $attributes->class([$sizes[$size]]) }} />
No autocomplete, and that's the daily friction. Icon names are filenames, not an enum. Mitigations, in order of payoff: (1) keep the constants file above so you only spell names once; (2) add the vendor SVG path to your IDE's search scope so fuzzy-find over vendor/codeat3/blade-emblemicons/resources/svg/ works; (3) resist building a generator — the set can change under you.
Wrong icon name = hard exception, not a blank space. Unable to locate a class or view for component [emblemicons-foo] thrown mid-render means a partially-rendered page in some layouts. In a Livewire component it becomes a 500 on the next update, not the initial load — which is why the smoke test matters. Debug by diffing your spelling against ls vendor/.../svg | grep -i foo; names are often kebab-case and hyphenation is the usual culprit (arrow-right vs arrowright, check-circle vs circle-check).
currentColor is the whole theming story, and it's the first thing to verify. If icons ignore text-red-500, something downstream is hardcoding fill="#000". Confirm by inspecting the rendered SVG source. Some upstream icon sets ship multi-color or hardcoded fills; if a specific icon won't recolor, that's an attribute of that one file, not a package bug — check the raw SVG in vendor/ before filing anything.
Sizing: class is not the same as width/height. If an SVG has explicit width="24" height="24" attributes, a class="size-5" may not win depending on CSS specificity and whether the attribute is presentational. Prefer utility classes that set both dimensions (size-5, or w-5 h-5 — don't mix w-* with h-* inconsistently across files) and check one icon's computed size in the browser devtools once, at adoption.
shrink-0 in flex containers. Icons inside flex rows with long sibling text will squash into ellipses. Add shrink-0 to the icon in your wrapper default and stop thinking about it. This is the most common "the icons look weird" bug report in any Blade app.
Config quirks — publish only if you must, and re-publish on upgrade. The config carries path, prefix, and disk. If you've published it, an upgrade that moves the vendor path will silently break every icon with a confusing "component not found" until you re-publish or hand-edit. If you don't need to change defaults, don't publish — you inherit the package's own correct config, which is one less thing to sync.
Changing the prefix is a repo-wide refactor. Setting prefix to something else renames every component tag. Do it at adoption if you're going to do it at all — never after the first screen ships.
Pin ^1.6, and read the changelog on every minor. Version 1.6.0 was purely a Laravel 13 constraint widening (PR #8) — nothing to change at the call site. But the risk profile here is that minor bumps can carry upstream icon renames, and there's no compatibility guarantee on names. Pin, and make "check for icon renames" an explicit step in your Laravel-upgrade checklist so the constraint change and the name audit happen together.
Two icon packages in one tree is a design liability, not a technical one. Before adopting, run:
composer show | grep -i icon
If you already have Heroicons, Lucide, or similar, decide which one wins per surface, or you'll get a size-5 Heroicon next to a w-5 h-5 Emblemicon and no one will own the inconsistency.
Debugging checklist, in order, when an icon doesn't appear. (1) Confirm the tag prefix matches config — x-emblemicons-* vs a published override. (2) Confirm the name exists in vendor/.../svg/. (3) php artisan view:clear — component auto-discovery caches aggressively, and a stale compiled view will show a "component not found" for an icon that plainly exists. (4) Inspect the rendered HTML: is <svg> present but invisible (fill/color/size), or absent entirely (resolution failure)? That single question splits the diagnosis in half.
Extension points are limited by design, and that's fine. There's no icon registry to mutate, no render hook, no theme layer — the package registers a path and a prefix and gets out of the way. If you need real control (variants, per-icon defaults, swapping sets), build it above the package in your own x-icon wrapper. That keeps the package disposable and your app's icon API stable regardless of what happens upstream.
Escape hatch, pre-agreed. If the maintainer stops tracking Laravel majors or an icon you depend on is removed, the fix is to copy the handful of SVGs you actually use into resources/svg/ and point your x-icon wrapper at local files (or at @svg() with a local path). Because you routed everything through a wrapper and a constants file, this is a small, mechanical change — hours, not a project. Keep that door open by never using <x-emblemicons-* /> directly inside domain views.
How can I help you explore Laravel packages today?