codeat3/blade-evil-icons
Blade Evil Icons provides easy, expressive Blade components for the Evil Icons SVG icon set in Laravel. Drop icons into your views with a simple x-evil-icon tag, control size and color via attributes, and keep your markup clean without extra JavaScript or asset pipelines.
Install and confirm discovery.
composer require codeat3/blade-evil-icons:^1.7
composer show codeat3/blade-evil-icons # confirm resolved version + constraints
There is no publish step, no config file, no service provider to register — Laravel package auto-discovery picks it up. The only thing worth checking before you write a single template is the constraint set in the installed composer.json: whether ^1.7 widens the Laravel range (e.g. 10–13) or pins to 13 only. That single fact decides whether your other environments can even composer update.
Look here first — the actual icon list. Don't trust blog posts or the upstream Evil Icons website; the package ships the set as files and the name attribute maps to the filename:
ls vendor/codeat3/blade-evil-icons/resources/svg | sed 's/\.svg$//' | column -c 100
That is your authoritative vocabulary (search, close, close-o, chevron-down, sc-github, spinner-2, …). Bookmark the command; it's faster than any docs page.
First use case — one icon in a layout. Drop it into resources/views/layouts/app.blade.php where you currently have an <img src="/icons/search.svg"> or a Font Awesome <i>:
<button type="submit" class="inline-flex items-center gap-2 text-slate-600 hover:text-slate-900">
<x-evil-o name="search" class="w-5 h-5" aria-hidden="true" />
<span class="sr-only">Search</span>
</button>
class and any other attribute you pass land on the root <svg>, so w-5 h-5, text-*, opacity-*, and aria-hidden all work without configuration. Render the page, inspect the DOM, and confirm you see a real <svg> element rather than an empty box — that's the whole install verified.
Wrap the set once, use your own tag everywhere. The highest-leverage day-to-day move is to never sprinkle <x-evil-o> through templates. Create resources/views/components/icon.blade.php:
@props(['name', 'size' => 'w-5 h-5'])
<x-evil-o
:name="$name"
{{ $attributes->class([$size, 'shrink-0']) }}
aria-hidden="true"
focusable="false"
/>
Then application code writes <x-icon name="gear" class="text-slate-400" />. When you outgrow Evil Icons (small, legacy, effectively frozen set), swapping to a multi-set blade-icons package is a one-file change instead of a repo-wide grep.
Drive icons from data, not from markup. Nav and action menus belong in config/arrays, with the icon name as a string:
// config/navigation.php
return [
['label' => 'Dashboard', 'route' => 'dashboard', 'icon' => 'chart'],
['label' => 'Settings', 'route' => 'settings', 'icon' => 'gear'],
];
@foreach (config('navigation') as $item)
<a href="{{ route($item['route']) }}" class="flex items-center gap-3 px-3 py-2 rounded hover:bg-slate-100">
<x-icon :name="$item['icon']" />
{{ $item['label'] }}
</a>
@endforeach
This makes icon usage greppable and testable — which matters more than it sounds (see the render test below).
Test icon names in CI, not in staging. A bad name fails at render time, and it fails deep inside a loop you may not have loaded. One cheap test covers every string you use:
it('renders every configured icon', function () {
foreach (collect(config('navigation'))->pluck('icon')->unique() as $name) {
$this->blade("<x-icon name=\"{$name}\" />")->assertSee('<svg', false);
}
});
Sizing and colour conventions. Because output is inline SVG inheriting currentColor, treat icons as text: set colour with text-* on the parent and size with w-* h-* (or Tailwind v4 size-5). Don't set fill/stroke classes per-icon unless you've inspected the emitted markup — Evil Icons paths carry their own presentation attributes, and a blanket fill-current on a stroke-based glyph can make it disappear. If you need to fine-tune weight, target descendants once you've confirmed the structure:
<x-icon name="close" class="size-4 [&_path]:stroke-[2]" />
Rollout pattern: additive sweep, layout by layout. Replace ad-hoc <img src="/icons/...">, hand-copied inline <svg>, and <i class="fa ..."> tags one template at a time, keeping the legacy asset directory in place. Decommission /public/icons and the FA dependency only after grep -rn 'icons/\|fa-' resources/views comes back clean. Keep this sweep out of the same deploy as a Laravel major upgrade so a rollback is attributable to one change.
Escalation path for icon-dense pages. If a single route renders 50+ icons, inline SVG starts to hurt: emit a local <symbol> sprite partial once per request and use <use href="#evil-search"> afterwards. It's a page-local optimisation, not something to do on day one.
The prefix is fixed at evil-o. You can't rename it via config, which is exactly why the wrapper component above earns its keep. The set choice also has a long tail: Evil Icons is small and static, so commit to it deliberately, not per-feature.
Names are filenames, verbatim. close-o, spinner-2, sc-linkedin — hyphenated variants are real and typos produce a Blade component/argument exception at render, not a blank space. When an icon "is missing", run the ls command from Getting Started before debugging anything else.
Invisible icons are almost always colour inheritance. Inline SVG uses currentColor; if a parent sets text-transparent, text-white on white, or if a global reset applies fill: none / svg { display: block } across the app, icons vanish. Check by wrapping one icon in a <div style="color:red"> — if it appears, it's CSS, not the package.
Accessibility is your job, not the package's. The component emits bare inline SVG. Default to aria-hidden="true" focusable="false" for decorative icons and pair meaningful ones with visible or sr-only text; adding a <title> child isn't part of the tag-based API. Verify attributes actually land on the root <svg> in your version before relying on role/aria-label passthrough, and validate against your a11y requirements before customer-facing use.
CSP is a point in its favour, not a risk. Inline SVG needs no img-src or font-src, which is why this beats icon fonts and <img> for strict policies. Just don't pipe icon markup through user-content sanitizers or {!! !!} blocks.
Caching and upgrades. After a composer update or when adding your own SVGs, run php artisan view:clear (and the blade-icons cache clear if enabled) before concluding the package broke. Pin ^1.7 and commit composer.lock so CI and prod resolve identically; the realistic failure mode here is a constraint mismatch on a framework upgrade, not a runtime bug.
Livewire and payload size. Inline SVG is re-serialised with every Livewire component render. Keep decorative icons in the surrounding Blade layout rather than inside components that update on every keystroke, and check the network payload if a form-heavy screen feels sluggish.
Email is a hard stop. Blade mailables and notifications render this fine, but Gmail and Outlook strip inline SVG. Use PNG fallbacks in resources/views/vendor/mail — don't let an icon refactor quietly break transactional email.
Exit strategy, cheap to buy now. Keep a vendored copy of the SVG set (e.g. resources/svg/evil-icons/) and a NOTICE file with the Evil Icons attribution alongside the package's MIT licence. Single-maintainer, zero-dependent commodity dependencies should always be swappable — and with the wrapper component in place, swapping means editing one file and re-pointing it at your vendored copies.
How can I help you explore Laravel packages today?