codeat3/blade-govicons
A Laravel Blade package that brings the Govicons icon set into your views as easy-to-use, customizable Blade components, letting you add government-themed icons with minimal markup.
Adoption is one Composer line. There is no publish step, no migration, no npm install, and no JS bundle — the service provider auto-discovers and registers every SVG in the package as a Blade component:
composer require codeat3/blade-govicons
<x-govicon-flag class="size-5 text-slate-600" />
Where to look first, in this order:
Confirm the component prefix before you write a single template. The prefix is either govicon- or govicons-, and getting it wrong is the worst failure mode in this package: an unknown <x-...> tag is treated as a plain custom element and echoed to the DOM, so you get an empty render with no exception, no log line, and nothing in laravel.log. Five minutes here saves an hour later:
ls vendor/codeat3/blade-govicons/resources/svg | head -50
That listing doubles as your icon inventory. Read it end to end against the README example, and note which names map to real UI needs in your app. If fewer than a handful do, stop — the dependency isn't earning its place.
Check the constraint surface against your lockfile, not the changelog. 1.6.0 is a narrow illuminate/support bump for Laravel 13. The realistic conflict is blade-ui-kit/blade-icons, which this package depends on and which other icon packages also constrain:
composer why blade-ui-kit/blade-icons
cat vendor/codeat3/blade-govicons/composer.json | grep -A6 '"require"'
Publish the config only if you actually need to change the prefix or icon path. Most apps won't:
php artisan vendor:publish --provider="Codeat3\BladeGovIcons\BladeGovIconsServiceProvider"
First use case. Don't seed adoption with a one-off icon in a random template. Pick the highest-traffic civic surface — a global nav entry, a service-category badge, an agency identifier in a table row — and replace a hardcoded <svg> blob there. That establishes the pattern in code review before it spreads, and gives you an immediate read on whether the set's naming and visual weight match the rest of your UI.
Anything you'd put on an <svg> passes straight through. The icons inherit currentColor, so colour is a text utility, not a fill:
<x-govicon-shield class="size-4 text-emerald-600" />
<x-govicon-building-library class="size-8 text-slate-400" aria-hidden="true" />
This is the one pattern worth adopting before wide usage. It pins sizing and accessibility defaults in a single file, and turns "swap icon library" or "pin the other prefix" into a one-line edit instead of a grep-and-replace across resources/views:
{{-- resources/views/components/ui/icon.blade.php --}}
@props(['name', 'size' => 'size-5'])
<x-dynamic-component
:component="'govicon-' . $name"
{{ $attributes->merge([
'class' => $size,
'aria-hidden' => 'true',
'focusable' => 'false',
]) }}
/>
Every call site then becomes <x-ui.icon name="shield" class="text-emerald-600" /> — uniform, greppable, and insulated from upstream naming.
Icon keys from a database column, a CMS field, or a settings table should always route through a map with a fallback. A typo in a concatenated name renders blank with no error, which surfaces as a "broken row" bug report rather than a stack trace:
// config/icons.php
return [
'agency' => 'building-columns',
'service' => 'hand-holding-heart',
'permit' => 'file-certificate',
'alert' => 'exclamation-triangle',
];
<x-ui.icon :name="config('icons.' . $row->icon_key, 'flag')" />
The ?? 'flag' default does real work: it degrades to a generic glyph instead of an empty cell, and it gives you a single audit point when the icon set changes.
<select>If you're shipping an admin picker, derive options from the filesystem once and cache them — reading the SVG directory on every render is a cheap-looking N+1:
$names = Cache::rememberForever('govicons.inventory', fn () => collect(
File::files(base_path('vendor/codeat3/blade-govicons/resources/svg'))
)->map(fn ($f) => $f->getFilenameWithoutExtension())->sort()->values()->all());
blade-icons gives you @svg() and svg() for contexts where a component tag is awkward — string-built markup, or a computed name inside a loop:
@svg('govicon-flag', 'size-5 text-slate-500')
{{ svg('govicon-' . $name, 'size-4') }}
Use these sparingly. They bypass your <x-ui.icon> wrapper, which means they bypass the a11y defaults too — each direct call is a small accessibility debt.
aria-hidden="true" is correct for decorative icons and wrong for icon-only controls. For the latter, label the control, not the SVG:
<button type="submit" aria-label="Download permit">
<x-ui.icon name="download" />
</button>
If an icon genuinely carries meaning alone — a status indicator in a data table — it needs an accessible name (<title> or adjacent visually-hidden text). Handle that with a dedicated wrapper variant rather than un-wrapping the component ad hoc; the default wrapper strips <title>.
GovIcons is a vertical set, not a general one. Write the rule down: civic semantics (agencies, services, public infrastructure, benefits and regulatory flows) come from GovIcons; utility UI (chevrons, search, close, menu, spinner) comes from Heroicons or Lucide. Without a stated rule you get three libraries mixed inconsistently inside a sprint, and nobody can explain why the same chevron looks different in two places.
Alongside config:cache and route:cache, add the icon registry build to your deploy script:
php artisan icons:cache
And to the rollback path:
php artisan icons:clear
Skipping the clear on rollback is the classic stale-registry bug: components present in the new release but absent in the old (or vice versa) resolve inconsistently across web nodes.
A wrong prefix fails in complete silence. No exception, no log entry, no console warning. The tell is in the rendered HTML source — you'll see <x-govicon-flag></x-govicon-flag> sitting in the DOM untouched. That immediately tells you it's a naming/registration problem, not a CSS one, and stops you from debugging styles for twenty minutes.
Run view:clear before you debug anything else. After a package upgrade, a namespace change, or a composer update, compiled views can hold a stale component registry and throw "component not found" or render blank. That looks like package breakage and isn't:
php artisan view:clear && php artisan icons:clear
Re-run the render before you go reading service-provider source.
The real version risk is blade-icons, not Laravel. 1.6.0 is a one-line illuminate/support bump. If your lockfile pins an older blade-icons, or another icon package constrains it tightly, that's where composer update fails. Resolve it on a branch before committing to adoption.
Pin the Laravel window deliberately. If you're on Laravel 10–12, read vendor/codeat3/blade-govicons/composer.json and confirm 1.6.0 hasn't narrowed the constraint set for you; if it has, stay on 1.5.0 and fold the bump into your framework upgrade. The changelog documents intent — the manifest documents reality.
Style with text-*, never fill or stroke. Every icon inherits currentColor. A hardcoded fill somewhere up the tree silently breaks dark mode, hover states, and any group-hover:text-* you've set up. If an icon refuses to change colour, suspect a hardcoded fill before you suspect the package.
Sizing is not automatic. There's no viewBox-derived default. An icon with no class renders at whatever ambient font-size and svg CSS resolves to, which is usually wrong and inconsistent with everything around it. Always pass a size — which is exactly why the wrapper defaults to size-5.
Don't build load-bearing UI abstractions on top of this. Two stars, zero dependents, and a release history that is essentially one compatibility bump is fine for a leaf asset dependency and insufficient for anything you can't remove in an afternoon. The mitigation is that it's trivially forkable — MIT SVG assets plus a registration provider — so the real contingency plan is copying the SVGs into your own path. Treat that as the plan and skip formal vendor review.
Re-verify at every Laravel major. 1.6.0's Laravel 13 support is a passing check, not a durable guarantee. Put it on the upgrade checklist alongside your other leaf dependencies rather than assuming it follows.
Extension points are narrow, and that's fine. The only real knobs are the component prefix and icon path in the published config. When you need a custom civic glyph the set lacks, the cleanest route is registering your own directory with BladeIcons::registerPath() (or dropping the SVG into your app's resources/svg), not forking the package — you keep the upstream update path open, and call sites still go through <x-ui.icon>.
How can I help you explore Laravel packages today?