codeat3/blade-typicons
Blade Typicons brings the Typicons icon set to Laravel as simple, self-closing Blade components. Render crisp SVG icons anywhere in your views with a clean tag syntax, no extra setup required.
Install and settle the prefix question first — it's the only real unknown.
composer require codeat3/blade-typicons
No provider registration, no publish step, no config file needed. Auto-discovery handles wiring, and the first icon renders from a cold install.
Pin immediately, in the same commit:
"codeat3/blade-typicons": "^1.8.0"
Zero stars, zero dependents, one maintainer. ^1.8.0 on Laravel 13; if you're on 10–12 and haven't verified the install, ^1.7.0 is the safer floor.
The prefix is genuinely ambiguous in the docs. The README shows <x-typicons::icon-name />; the wrapper convention these packages usually follow is a flat <x-typicon-name />. Drop three candidates into a throwaway route and use whichever renders — five seconds of work beats twenty minutes of reading Blade Icons source:
<x-typicon-home /> {{-- most likely --}}
<x-typicons-home /> {{-- plural variant --}}
<x-typicons::home /> {{-- namespaced variant --}}
Where to look, in priority order:
vendor/codeat3/blade-typicons/resources/svg/ — the only authoritative icon-name list. ls it before you design a single screen. Every other source drifts.vendor/codeat3/blade-typicons/composer.json — the illuminate/support and blade-ui-kit/blade-icons constraints for the version you actually have.vendor/blade-ui-kit/blade-icons/README.md — the real documentation for attribute merging, set overrides, and sprite generation. This package contributes SVGs to that system; it doesn't document it.First real use case: a nav item. It exercises install, name resolution, attribute forwarding, and sizing in one block, and failures are visible rather than buried in a table cell:
<a href="{{ route('dashboard') }}"
class="flex items-center gap-2 px-3 py-2 rounded {{ request()->routeIs('dashboard') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-50' }}">
<x-typicon-home class="w-5 h-5 shrink-0" />
<span class="text-sm font-medium">Dashboard</span>
</a>
If that renders, you're done with setup. Everything after is architecture.
Immediately after it renders, wrap it. Not next sprint — now, while there are three icons in the codebase. See Implementation Patterns.
1. The wrapper is the whole strategy. This is the highest-leverage decision with any single-vendor icon package, and with a 0-star one it's closer to mandatory. One internal component makes the set swappable without touching a single view.
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'size' => 'md'])
@php
$sizes = [
'xs' => 'w-3 h-3',
'sm' => 'w-4 h-4',
'md' => 'w-5 h-5',
'lg' => 'w-6 h-6',
'xl' => 'w-8 h-8',
];
$icon = config("icons.map.{$name}", $name);
$icon = in_array($icon, config('icons.allowed', []), true) ? $icon : config('icons.fallback', 'warning');
@endphp
<x-dynamic-component
:component="'typicon-' . $icon"
{{ $attributes->class([$sizes[$size] ?? $sizes['md'], 'shrink-0']) }}
aria-hidden="true"
focusable="false"
/>
x-dynamic-component resolves at render time rather than compile time, which keeps the wrapper to a single file. Every view then writes <x-icon name="save" size="sm" /> and never mentions the vendor.
2. One semantic map, one file. Icon names describe pictures (arrow-right, tick, cog); your views need intent (next, save, settings). The moment a designer renames something or you swap sets, a semantic map turns an audit into a one-line edit:
// config/icons.php
return [
'map' => [
'save' => 'tick',
'delete' => 'trash',
'settings' => 'cog',
'warning' => 'warning',
'empty' => 'folder',
'next' => 'arrow-right',
'back' => 'arrow-left',
],
'fallback' => 'warning',
'allowed' => ['tick', 'trash', 'cog', 'warning', 'folder', 'arrow-right', 'arrow-left'],
];
It doubles as your inventory of actually-used icons — the exact list you'd work from if you ever extracted the SVGs and dropped the dependency.
3. Let CSS own size and color; the component owns nothing. The root <svg> inherits currentColor, so a class on a parent control recolors the icon for free:
<button class="btn {{ $isActive ? 'text-white' : 'text-gray-600 hover:text-indigo-600' }}">
<x-icon name="settings" size="sm" />
Settings
</button>
Resist scatter-shot inline width/height attributes — they beat your utility classes and produce dark-mode, print, and responsive surprises that are miserable to trace back.
4. Table loops are where the package pays for itself. Uniform repeated cells, no markup duplication:
@foreach ($users as $user)
<tr>
<td class="px-4 py-2">
<x-icon :name="$user->is_active ? 'tick' : 'times'"
size="sm"
class="{{ $user->is_active ? 'text-green-600' : 'text-red-400' }}" />
<span class="ml-1">{{ $user->name }}</span>
</td>
<td class="px-4 py-2 text-right">
<a href="{{ route('users.edit', $user) }}" aria-label="Edit {{ $user->name }}">
<x-icon name="edit" size="sm" class="text-gray-500 hover:text-indigo-600" />
</a>
</td>
</tr>
@endforeach
Names stay literal or config-derived. Never a DB column, never request input.
5. Accessible names go on the control, not the SVG. The component's output is decorative; aria-hidden="true" in the wrapper handles that once for the whole app. When an icon is the only content of a button or link, the label belongs on the interactive element:
<button type="button" aria-label="Delete record" class="p-2 rounded hover:bg-red-50">
<x-icon name="trash" size="sm" class="text-red-600" />
</button>
Don't try to inject a <title> into the SVG. Attribute forwarding makes what lands in the output inconsistent across versions, and a <title> on a decorative SVG produces noisy screen-reader output layered on top of your aria-label.
6. Same code in Livewire, mailables, and PDFs — with two adjustments. Server-rendered Blade, no JS runtime. That's the real workflow advantage over a JS icon library.
php artisan view:cache in production regardless.<style> and can't see Tailwind, so the utility classes vanish. Inline the styling on the SVG instead:<x-icon name="tick" style="width:16px;height:16px;color:#16a34a;" />
7. One smoke test, pointed at the map. Cheap, and it's the only thing between a patch release and a silently blank icon somewhere in production:
public function test_all_mapped_icons_render(): void
{
foreach (config('icons.map') as $semantic => $icon) {
$this->blade("<x-icon name=\"{$semantic}\" />")->assertSee('<svg', false);
}
}
Because it iterates the map, it grows automatically as you adopt icons — no test maintenance.
8. Keep the exit plausible at all times. Wrapper + config map + never using raw <x-typicon-*> tags in views means leaving is: copy the ~15 SVGs you use into resources/svg, register them via Blade Icons or a trivial own component, change one line in icon.blade.php, drop the dependency. An afternoon, not a migration.
Silent failure is the default failure mode — internalize this first. A typo'd or removed icon name renders nothing: no exception, no log line, no console warning. A layout gap is your only signal. When a page "looks wrong," view-source and search for <svg where the icon should be. Absent? The name is wrong. Then go straight to vendor/codeat3/blade-typicons/resources/svg/ and read the actual filename. This single scenario burns hours because people hunt for an error that was never thrown.
Never invent a name from appearance. Typicons are hyphenated (arrow-right, th-large, arrow-sync) and collisions with intuition are common — is it th-large or th-list? cog or settings? Copy from the vendor directory every time.
Namespace collisions if you already run other Blade Icons sets. Heroicons and friends register through the same blade-icons config. Wrong glyphs or unresolved components mean a set conflict:
php artisan vendor:publish --tag=blade-icons
// config/blade-icons.php
'sets' => [
'typicon' => [
'path' => 'vendor/codeat3/blade-typicons/resources/svg',
'prefix' => 'typicon',
],
],
Keep your prefix distinct from any existing default set so the swap path stays open.
Compiled views will lie to you. After a package upgrade, an edit to <x-icon>, or a change to the config map, run php artisan view:clear. In production, "icons vanished after deploy" is nearly always stale compiled Blade views plus a warm opcache. Pair view:clear with view:cache.
Licensing: MIT package, non-MIT artwork. The wrapper is MIT; the Typicons artwork carries its own license (CC BY-SA family), which typically obliges attribution. The package's MIT badge reads as "no strings attached" and that reading is wrong for the icons. Confirm with whoever owns brand/compliance before broad rollout and record the obligation somewhere durable — a LICENSES.md, an about screen. If you can't satisfy attribution, don't adopt.
Inline SVG volume is a rendering-strategy problem, not a package bug. 200 rows × 3 icons = 600 inline <svg> nodes in the document. Fine at normal volume; noticeable on dense tables and kanban boards. Escalation ladder, cheapest first: view:cache in production → drop icons from per-row repeated cases → move hot icons into an <x-sprite> rendered once per page and referenced with <use href="#icon-trash">. Note <use> requires the sprite in the same document, so it breaks in email clients — keep it out of mailables.
Override a single icon without forking. The SVGs are plain files consumed by Blade Icons, so copying one into your own resources/svg under the same set name wins. It's simultaneously the customization path and the abandonment insurance.
Don't feed user input into the component name. <x-dynamic-component :component="$request->input('icon')" /> resolves arbitrary Blade components, including ones you never meant to expose. Go through the config whitelist with a known-good fallback (as in the wrapper above) rather than passing raw strings.
Read the changelog before every bump. 1.8.0 is a narrow Laravel 13 compatibility PR — no features, no API change. Confirm it didn't raise the PHP floor or narrow illuminate/* past what you run:
composer why-not codeat3/blade-typicons 1.8.0
Audit coverage before committing, not in sprint three. Typicons is a compact utility set (~336 glyphs) — no brand marks, no illustrations, no multi-tone. Walk your actual screens (nav, empty states, notifications, table toolbars, date pickers) against resources/svg in an afternoon. Discovering mid-feature that there's no acceptable "upload cloud" is a far more expensive conversation than an upfront gap check. Plan for hand-rolled SVGs alongside this package; don't expect full coverage.
How can I help you explore Laravel packages today?