codeat3/blade-clarity-icons
codeat3/blade-clarity-icons is a Laravel Blade package for using Clarity Icons in your views. It provides simple Blade components to render any Clarity icon as inline SVG, making icons easy to add, style, and customize in PHP applications.
Install. One command, no build step, no JS, no published assets:
composer require codeat3/blade-clarity-icons
Auto-discovery registers the service provider and the Blade component namespace. There is nothing to register in config/app.php, nothing to compile, nothing to add to vite.config.js.
First render. Put an icon in any Blade view — the component name is derived from the Clarity icon name, prefixed with clarity-icon-:
<x-clarity-icon-alarm />
That is the whole integration. If it renders, you're done with setup. If it doesn't, the failure is almost always stale compiled views — run php artisan view:clear before debugging anything else.
Where to look first. Two places, in this order:
vendor/codeat3/blade-clarity-icons/resources/views/components/ is the authoritative icon index. This package ships hundreds of components and there is no documented Artisan command to list them, so ls on that directory is your catalog:
ls vendor/codeat3/blade-clarity-icons/resources/views/components/ | head -50
composer.json in the installed version. Confirm the Laravel constraint widened to include your major rather than shifted. ^1.11 is the Laravel 13 line; Laravel 10–12 teams pin ^1.10. Check the PHP floor at the same time — Laravel 13 support implies PHP 8.3+.First use case worth building. Don't start with the icon; start with the wrapper. Before the second icon tag lands in a template, create one semantic component:
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'label' => null])
<x-dynamic-component
:component="'clarity-icon-' . $name"
{{ $attributes->merge(['class' => 'w-5 h-5 shrink-0']) }}
@if ($label)
role="img" aria-label="{{ $label }}"
@else
aria-hidden="true"
@endif
/>
Then <x-icon name="alarm" /> everywhere. This is the single highest-leverage decision in the whole adoption: it decouples templates from the package's naming, gives you one place to fix sizing, and turns "replace the icon library" from a sprint into an afternoon. Prove the pattern on one internal/admin page, and only then let it spread.
Sizing and color via attributes. The SVGs render inline, so they participate in the normal CSS cascade. In practice:
{{-- Tailwind text color + font-size-derived sizing --}}
<x-clarity-icon-alarm class="w-5 h-5 text-amber-500" />
{{-- Explicit color --}}
<x-clarity-icon-check class="text-emerald-600" style="width: 1.25rem" />
If the component forwards $attributes to the <svg>, a class of w-4 h-4 is usually enough. Verify this once against your actual installed version — if the SVG carries hardcoded width/height, CSS will still win because presentation attributes have the lowest cascade priority, but not if the SVG uses inline style. A one-line global fallback makes this a non-issue:
{{-- in your layout, or a base layer --}}
<style>svg { width: 1em; height: 1em; }</style>
Dynamic icon names. Loops and data-driven UI need x-dynamic-component, not string interpolation into a tag:
@foreach ($statuses as $status)
<x-icon :name="$status->icon" :label="$status->label" />
@endforeach
{{-- wrapper internals: resolve at runtime --}}
<x-dynamic-component :component="'clarity-icon-' . $name" {{ $attributes }} />
Blade cannot interpolate the tag name itself (<x-clarity-icon-{{ $name }} /> is invalid). If your icon names are configurable at runtime, this is the only correct path.
Accessibility convention, decided once. The package renders a bare <svg>; it makes no accessibility promise for you. Set the rule centrally and encode it in the wrapper:
aria-hidden="true", no label.role="img" plus aria-label, or a <title> element matching aria-labelledby.<button> or <a> owns the accessible name. Do not put aria-label on the SVG and the button — screen readers will announce it twice in inconsistent ways.Making this a $label prop on the wrapper (as above) is the difference between a consistent system and per-feature improvisation.
Replacing existing icons incrementally. Most brownfield adoptions start with a mix of inline SVG partials and icon-font <i class="fa ..."> tags. Migrate behind the wrapper, one surface at a time:
{{-- before --}}
<i class="icon-cl-alarm"></i>
{{-- after --}}
<x-icon name="alarm" />
Leave the icon font installed until the last <i> is gone; running both systems in parallel is fine because the wrapper is the only call site. Never migrate and redesign in the same change — the visual regression diff becomes unreadable.
Livewire and Inertia/Blade surfaces. Both work unmodified where Blade renders server-side. In Livewire components, use the wrapper inside the Blade partial exactly as you would anywhere else — no special handling, and icons re-render on component updates with no flicker because the markup is inline. In Inertia, this only applies to the root Blade view and any server-rendered islands; a client-side Vue/React tree cannot use <x-clarity-icon-*> and should use a JS icon package or the raw SVG set.
Composition with buttons and links. Keep the icon dumb and let the parent own semantics and layout:
<button type="button" class="inline-flex items-center gap-2 ...">
<x-icon name="trash" class="w-4 h-4" />
<span>Delete</span>
</button>
This reads correctly, is accessible without extra ARIA, and survives an icon swap.
Performance posture for icon-dense pages. Inline SVG is a genuine win over CDN fonts and sprites: no network hop, no FOIT/FOUT, no third-party availability dependency. The cost is markup weight, which is negligible at 5–15 icons per page and material at 100+. If a page renders icons in a long table, wrap the row in a partial cache or move to a sprite for that view specifically:
@cache('icon-alarm-16', now()->addDay())
<x-clarity-icon-alarm class="w-4 h-4" />
@endcache
Don't pre-optimize — measure the heaviest real page, not the average one.
The component name has icon in the middle. It's <x-clarity-icon-alarm />, not <x-clarity-alarm />. This trips up almost everyone on first contact because the package name and the component prefix don't match, and the error is a ComponentNotFoundException that tells you nothing about the correct form. Check the resources/views/components/ directory listing before you guess.
Collisions with other icon packages. If you also run blade-ui-kit/blade-icons or any package that registers an overlapping namespace, resolution order decides which glyph wins — silently, in production, with no error. Two mitigations: publish the package config and set an explicit unique prefix, and enforce one icon package per application. If the config is published under a prefix like clarity-, every call site changes, so do it before the wrapper proliferates, not after.
Attribute forwarding is not guaranteed on every component. Before you rely on class="w-5 h-5" working everywhere, test on three or four icons. Some generated components merge $attributes onto the <svg>; others don't, in which case classes land on nothing and you get default-sized icons. The wrapper with {{ $attributes->merge([...]) }} gives you a single place to compensate, and the global svg { width: 1em; height: 1em } rule is a belt-and-braces fallback.
Stale view cache after install or upgrade. New components appear only after Blade recompiles. On a deploy that installs the package via composer install, the old compiled views can persist and every new tag 500s. Make this part of the deploy script:
composer install --no-dev --optimize-autoloader
php artisan view:clear
php artisan view:cache
Running view:cache at build time also surfaces component-resolution errors before traffic does — which is exactly the failure class this package produces.
Static analysis and IDE autocomplete won't help you. Hundreds of components with no PHPDoc or generated registry means no completion, no phpstan coverage, and no discovery outside ls. This is a soft argument for the wrapper, and a hard argument for a short "approved icon names" list in your repo's contributing docs — otherwise every developer reinvents a slightly different vocabulary for the same concepts.
The bundled SVG set is pinned. This package will not track VMware's upstream Clarity catalog automatically. Visual drift only arrives on a deliberate version bump, but it does arrive — attribute cleanups and path tweaks in upstream SVGs are the usual breakage class, and they show up as a one-pixel shift nobody notices for a quarter. Scope visual regression checks to the wrapper's surface after every bump.
Framework upgrades are the real risk, not defects. Version 1.11.0 is a pure Laravel 13 compatibility bump — no icon changes, no API changes — which is a good hygiene signal, but a reactive one. A Laravel major can leave a window where this dependency blocks the entire platform upgrade. Record it in your upgrade runbook, pin a caret range (^1.11 on 13, ^1.10 on older majors), and never dev-main. Because the blast radius is the whole migration rather than the icons, this is the failure mode worth planning for.
Single-maintainer bus factor — the escape hatch is cheap. Effectively one contributor, no SLA, no dependents recorded. Two mitigations, both under an hour of work if done at adoption time: (a) route everything through <x-icon> so replacement is contained; (b) if your risk appetite is low, vendor the generated SVG Blade files into your repo under your own namespace and drop the Composer dependency entirely. The underlying Clarity SVGs are public, so this is a genuine escape hatch rather than a hypothetical one — but only if you built the wrapper first.
Supply-chain check, once. For teams with review requirements: the SVGs are static markup, but audit them for embedded <script> or external href/xlink:href references before rollout so the check is documented rather than assumed:
grep -rE '<script|href="http|xlink:href' vendor/codeat3/blade-clarity-icons/resources/views/components/ | head
Clean output is the expected result and takes thirty seconds to confirm.
Don't attempt multi-icon-set consolidation during adoption. If you have Font Awesome, Heroicons, and Clarity all in play, migrate one surface to the wrapper, stabilize, then convert the others through the same wrapper. Doing it in one sweep means the visual regression has no clean baseline and the wrapper is untested against real variety when you need it most.
How can I help you explore Laravel packages today?