codeat3/blade-uiw-icons
Blade UIW Icons brings the UIW icon set to Laravel Blade as simple, cacheable components like . Install once, then drop any icon into your views with a single tag—no SVGs to copy, no extra setup, just clean, consistent icons across your app.
What you're actually installing. codeat3/blade-uiw-icons is a thin registration layer over blade-ui-kit/blade-icons. It ships the Uiw/Unicons SVG files and registers each one as a Blade component under the uiw prefix. There is no runtime, no JS, no asset pipeline — rendering is "look up filename, inline the SVG." Treat it as static assets with a naming convention, because that framing answers most of the questions you'll hit later.
Install and verify in three commands:
composer require codeat3/blade-uiw-icons
composer why-not codeat3/blade-uiw-icons 1.7.0 # confirms constraint resolution
php artisan about | grep -i icons # optional sanity check
Auto-discovery handles the service provider. You do not need to publish config, assets, or views to get a first render.
First render. Drop a component into any Blade view:
<x-uiw-home class="size-5 text-gray-500" />
<x-uiw-arrow-right class="size-4 shrink-0" />
The output is inline <svg> whose paths use currentColor, so text-* utilities drive the fill and size-* drives dimensions. No wrapper divs, no background-image hacks, no build step.
Where to look first — in this order:
vendor/codeat3/blade-uiw-icons/resources/svg/ — this directory is your component API. Run ls vendor/codeat3/blade-uiw-icons/resources/svg | head -50 and ... | wc -l. Every file maps to <x-uiw-{filename} />. Do not guess names from the upstream Unicons site; the packaged snapshot is what resolves.config/blade-uiw-icons.php (publish with php artisan vendor:publish --tag=blade-uiw-icons-config, then confirm the tag in php artisan vendor:publish output). The two keys you care about are the component prefix and the default CSS class applied to every icon.blade-icons docs, not this package's — caching, attribute handling, and x-dynamic-component behavior are all inherited from there.First real use case. Don't start with a new feature. Pick your main layout and replace hand-rolled <svg> blocks in the nav, sidebar, and buttons. That's a 30-minute change that validates naming, sizing, and color inheritance against code the whole team already looks at daily. Only after that inventory the rest of the app.
If you plan a Laravel 13 upgrade: 1.7.0 exists solely to widen the framework constraint. composer why-not is the gate — a declared constraint and a working install are different facts. Also check the blade-icons version in your lockfile supports Laravel 13; that's the constraint one level removed from the package you're evaluating.
Put a semantic wrapper in front of it on day one. The single highest-leverage thing you can do. Every view should reference your names, never x-uiw-* directly — that makes an icon-set swap a one-file change.
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'label' => null])
@php
$semantics = $label
? ['role' => 'img', 'aria-label' => $label]
: ['aria-hidden' => 'true'];
@endphp
<x-dynamic-component
:component="'uiw-'.$name"
{{ $attributes->merge($semantics)->merge(['class' => 'shrink-0']) }}
/>
Back it with an enum so editors and static analysis help you:
// app/Support/Icon.php
enum Icon: string
{
case Home = 'home';
case Settings = 'setting';
case Check = 'check-circle';
case Warning = 'exclamation-circle';
}
<x-icon :name="\App\Support\Icon::Check->value" class="size-4 text-green-600" />
Sizing and color. Everything goes through Tailwind utilities on the component tag — the SVG inherits currentColor, so text-*, hover:text-*, and group-hover:text-* all work without extra CSS. Set a baseline size in the wrapper (shrink-0 plus a default size-4) so callers only override when they mean to. Tailwind v3 uses w-4 h-4; v4 uses size-4 — pick per your setup.
Nav and action configs. Icons in data structures are where this package pays for itself. Keep the icon name in the config, resolve at render:
// config/navigation.php
return [
['label' => 'Dashboard', 'route' => 'dashboard', 'icon' => 'dashboard'],
['label' => 'Reports', 'route' => 'reports', 'icon' => 'chart-bar'],
];
@foreach (config('navigation') as $item)
<a href="{{ route($item['route']) }}" @class(['flex items-center gap-2', 'text-indigo-600' => request()->routeIs($item['route'])])>
<x-icon :name="$item['icon']" class="size-4" />
{{ $item['label'] }}
</a>
@endforeach
Dynamic icons from the database — always whitelist. x-dynamic-component will throw on an unregistered name, which is a runtime error in production, not a graceful degradation. Validate server-side:
$allowed = array_column(\App\Support\Icon::cases(), 'value');
$icon = in_array($status->icon, $allowed, true) ? $status->icon : 'circle';
<x-icon :name="$icon" class="size-4" />
Alternatively validate with Rule::enum(Icon::class) at the form-request layer so bad values never reach the DB.
Status and semantic mapping. Define the icon-per-state mapping once, next to your status enum, rather than repeating match expressions in a dozen views:
public function icon(): Icon
{
return match ($this) {
self::Active => Icon::Check,
self::Pending => Icon::Clock,
self::Failed => Icon::Warning,
};
}
Livewire. Nothing special — icons are server-rendered Blade, so they re-render with the component. Two notes: pass icon names (strings from your enum), not SVG markup, in public properties; and if an icon appears inside a wire:loading block, keep it out of the morph target or it will flicker.
Testing. A wrong or missing glyph passes code review and surfaces in QA. Cheap insurance: one test that renders every icon your app actually uses.
it('renders every icon in the allowlist', function () {
foreach (\App\Support\Icon::cases() as $icon) {
$this->blade("<x-icon name=\"{$icon->value}\" />")
->assertSee('<svg', false);
}
});
Also assert the semantic wrapper forwards attributes — assertSee('aria-hidden="true"', false) and a class="size-4" case. This catches icons:cache regressions and typos in one pass.
Deploy pipeline. With the cache warm, rendering is an array lookup instead of a filesystem scan:
php artisan config:cache
php artisan icons:cache # after config:cache
php artisan view:cache
Order matters — config:cache first. In local dev, php artisan icons:clear after you pull, or newly added icons won't resolve (see Gotchas).
Emails and PDFs. Inline SVG works in browsers and dompdf/snappy, but most desktop email clients strip it. Use PNG for mail; reserve these components for web UI.
icons:cache is a deploy step, not a local convenience. The cache is a serialized manifest written to bootstrap/cache/. If you ship a container image built with the cache warm, then mount or bake new SVGs later, they won't be in the manifest. Conversely, a developer who runs it locally will be confused for an hour when a component they just added 404s — php artisan icons:clear fixes it. Document both directions.
Prefix collisions are real if you run more than one codeat3/blade-* package. Heroicons, Tabler, Iconoir, and friends all default to short prefixes. If a sibling package already claims your configured prefix, you'll get whichever registration wins — silently wrong glyphs, not an error. Publish config for each and give them distinct prefixes (uiw, hi, tabler) before the second one lands. If you build the semantic wrapper, this only ever needs solving once.
Naming mismatches are the #1 source of surprise. The packaged set is a snapshot, so names can differ from the Unicons website (setting vs settings, chart-bar vs bar-chart, -o/-alt suffixes, version variants). Don't trust the upstream docs — ls the vendor SVG directory or grep it. Generate your enum from that directory listing on day one and you skip the whole class of error.
Some icons are stroke-based, some fill-based. stroke-width won't scale with size-*, and a stroke icon rendered at 16px with a 2px stroke looks heavier than its fill counterpart. Use your wrapper to normalize ([&_svg]:stroke-[1.5] or a variant key), or accept small visual inconsistency at small sizes — but check it at the sizes you actually ship before a designer finds it.
Accessibility doesn't come for free. Decorative icons need aria-hidden="true"; meaningful icons need role="img" + aria-label (or an adjacent visible text label, which is usually better). The wrapper above encodes that split via the label prop. An icon-only button with no accessible name is the most common audit failure introduced by icon packages.
x-dynamic-component throws, it doesn't degrade. An unregistered name produces a runtime exception at render. That's arguably good (loud), but it's a 500 for a bad CMS value. Always whitelist at the boundary, and give a fallback icon when the value is user-supplied.
Inline SVG is bytes. Fine for tens of icons per page; a real cost on icon-dense dashboards, tables with per-row status icons, or repeaters. Measure response size before optimizing, but know the mitigation: a sprite or <use> reference for high-density grids, or just paginate. Also note gzip compresses repeated inline SVG extremely well, so the practical threshold is higher than raw byte math suggests.
Don't dual-ship in an Inertia/Vue/React app. If the front end already imports @uiw/icons, adding this package means two runtimes, two upgrade surfaces, and duplicate payload. Pick one side of the boundary — server-rendered Blade for admin/internal surfaces is a legitimate split, but make it explicit.
Config changes require a rebuild. The prefix and default class live in config/blade-uiw-icons.php. With config:cache in production, editing that file does nothing until the next deploy. Change it early; changing a prefix after views exist is a grep-and-replace across the whole codebase.
Pin the version and commit the lockfile. Zero stars and a single maintainer is a real signal — you cannot cite community adoption in a go/no-go. Mitigate cheaply: pin ^1.7, commit composer.lock, and write the contingency into your notes. The exit is genuinely hours: copy resources/svg into your own directory, re-register under your own prefix, keep your semantic wrapper unchanged, and no view file moves. That low exit cost is the strongest argument for adopting now.
Resolve upstream Unicons attribution early. The wrapper is MIT, but the icon artwork's license and any attribution requirement are a separate question. Get an answer before it becomes launch-week legal review — it's a five-minute check if you do it in week one.
Verify compatibility release-by-release. Maintenance here is entirely "did the maintainer track the Laravel major?" 1.7.0 says yes for Laravel 13. Re-run composer why-not at every Laravel major and put a named owner on that annual check, or it will fall between teams and surface as a surprise blocker mid-upgrade.
How can I help you explore Laravel packages today?