codeat3/blade-gravity-ui-icons
A Laravel Blade component package for Gravity UI icons, letting you render any icon with simple x-icon tags in your Blade views. No SVGs to copy, no config—just clean, inline icon components for your PHP app.
Install it and render an icon in under a minute
composer require codeat3/blade-gravity-ui-icons
There is nothing else to wire up. No service provider registration, no asset publish, no npm step, no runtime JavaScript. Laravel's package discovery registers the provider, and blade-ui-kit/blade-icons (pulled in as a dependency) handles the actual SVG resolution. The icons land in your templates as Blade components:
<x-gravity-ui-icon-alarm class="w-5 h-5 text-red-500" />
Colour comes from currentColor, so Tailwind's text-* utilities work without touching fill or stroke. Size comes from w-*/h-*. Any HTML attribute you pass (class, aria-label, data-*, id) is forwarded to the root <svg>.
Where to look first, in order
vendor/codeat3/blade-gravity-ui-icons/resources/svg/ — the filename minus .svg is the component suffix. This directory is the authoritative catalogue. README tables drift; the directory does not.BladeGravityUiIconsServiceProvider) — confirms the exact registered prefix (gravity-ui-icon-* is the convention, but verify rather than guess) and shows how the set is registered with Blade Icons.prefix, a default class, and path overrides live (see below).First real use case: stop hand-pasting SVG
You were copying <svg> blobs from Figma into Blade files, or maintaining resources/views/components/icons/*.blade.php by hand and forgetting which one was chevron-down vs chevron-down-small. Now:
<button class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-white">
<x-gravity-ui-icon-plus class="w-4 h-4" aria-hidden="true" />
New item
</button>
The icon inherits the button's white text via currentColor, scales with the button's text size if you use h-[1em], and accepts attributes. Replacing thirty hand-pasted SVGs with thirty component calls is the entire value proposition, and it removes a recurring Figma-to-code chore permanently.
Publish the config only if you need to change the prefix
php artisan vendor:publish --tag=blade-gravity-ui-icons-config
Copy the tag from the README rather than reconstructing it from memory — codeat3 packages follow blade-<set>-config, but that one string is worth verifying. For most projects you never publish this file.
1. Wrap the set once, depend on the wrapper everywhere
The fastest way to waste the benefit of a shared icon set is to sprinkle <x-gravity-ui-icon-*> with ad-hoc sizing across dozens of views — you've reintroduced the fragmentation you just paid a Composer install to remove. Create a single design-system component instead:
{{-- resources/views/components/ui/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',
];
@endphp
<x-dynamic-component
:component="'gravity-ui-icon-' . $name"
{{ $attributes->merge(['class' => ($sizes[$size] ?? $sizes['md']) . ' shrink-0']) }}
aria-hidden="true"
/>
Consumers write <x-ui.icon name="person" size="lg" class="text-gray-400" />. When you swap icon sets, migrate to sprite sheets, or add a data-testid for E2E tests, you edit one file. It also gives you a single place to own the aria-hidden default (see the accessibility gotcha).
2. Data-driven icons via x-dynamic-component
This is the idiomatic seam for nav rails, menu tables, and CMS-driven dashboards:
// config/navigation.php
return [
['label' => 'Dashboard', 'route' => 'dashboard', 'icon' => 'house'],
['label' => 'Settings', 'route' => 'settings', 'icon' => 'gear'],
['label' => 'Reports', 'route' => 'reports', 'icon' => 'chart-line'],
];
@foreach (config('navigation') as $item)
<a href="{{ route($item['route']) }}" class="flex items-center gap-2">
<x-dynamic-component :component="'gravity-ui-icon-'.$item['icon']" class="w-4 h-4" />
{{ $item['label'] }}
</a>
@endforeach
The icon name comes from a config value written by a developer here, so it's safe. The moment it comes from a request or a user-editable database column, read the injection gotcha below before you ship.
3. Reach for @svg in loops, components everywhere else
Because the set registers through Blade Icons, the set-level helpers work too:
@svg('gravity-ui-alarm', 'w-5 h-5 text-blue-600')
The @svg directive skips Blade component class instantiation and is measurably faster in tight loops — a data table rendering hundreds of rows each with two action icons is exactly where you'd notice. Use components in ordinary templates for readability; use @svg when you're generating markup in bulk.
The svg() helper is the PHP-side escape hatch, useful for notification bodies, mail templates, and array-built table cells:
$html = collect($alerts)->map(fn ($a) => svg(
'gravity-ui-'.$a['icon'],
'w-4 h-4 text-'.$a['severity']
)->toHtml());
4. Cache the set on every deploy
Blade Icons compiles the whole SVG directory into a single PHP file, so per-request filesystem reads disappear entirely:
composer install --no-dev --optimize-autoloader
php artisan icons:clear # see the stale-cache gotcha
php artisan icons:cache
php artisan view:cache
php artisan config:cache
php artisan route:cache
For icon-heavy pages this is the difference between dozens of file reads and one array lookup. For a page with three icons it's noise — but add it to the deploy script unconditionally anyway. Consistent caching beats selective caching you have to remember.
5. Pair it correctly with your stack
text-* for colour, w-*/h-* for size. The icons use fill="currentColor" — you almost never need a fill-* utility.icons:cache makes cheap — another argument for step 4.<html> shell, layout chrome, and meta tags. It does not work inside .vue or .jsx templates. For those, use the upstream @gravity-ui/icons JS package.6. Empty states, alerts, and toasts — where consistency earns its keep
Success banner, destructive confirmation, empty search results. One line each, all visually aligned without design review:
<div class="flex items-start gap-3 rounded-lg bg-green-50 p-4">
<x-gravity-ui-icon-circle-check class="w-5 h-5 text-green-500 shrink-0" aria-hidden="true" />
<p class="text-sm text-green-800">Saved successfully.</p>
</div>
Icon names are kebab-cased and must match the filename exactly. Gravity UI's source names are PascalCase (ChevronDown, ArrowUpFromSquare); the package lowercases and hyphenates them (chevron-down, arrow-up-from-square). A wrong name renders nothing — no exception, no placeholder, no console warning. Your first debugging step is always:
ls vendor/codeat3/blade-gravity-ui-icons/resources/svg | grep chevron
This silent-failure mode is the single most-reported "bug" against the entire codeat3/blade-*-icons family. Build the habit: wrong icon → ls the SVG directory, not Stack Overflow.
These are filled paths, not strokes. Gravity UI icons predominantly use fill="currentColor". Adding stroke-red-500 or fill-none does nothing useful, and fill-none in particular can blank the icon entirely. Colour with text-*. If you need a stroked variant, that's a different icon set — not a CSS override.
class merges only onto the root <svg>. You cannot target inner paths from the component call. Arbitrary variants like [&_path]:opacity-50 work if you really must, but if you're fighting per-path specificity you're off the paved road — copy that one SVG into your own registered directory and own it.
User-supplied icon names are an injection surface. :component="'gravity-ui-icon-'.$request->icon" lets a request enumerate or probe arbitrary component names, producing confusing resolution errors and leaking what's installed. Validate against an allowlist:
$icon = in_array($item->icon, config('icons.allowed'), true)
? $item->icon
: 'circle-question';
Better still, type the column as a backed enum and validate at write time — then the template can't go wrong.
icons:cache goes stale on every upgrade. Icons added by a version bump won't appear, and — worse — icons removed by a version bump keep rendering from the old cache file. This is the classic Friday-afternoon time-sink: you install 1.4.0, the changelog says "Laravel 13 compatibility," and suddenly new icons don't resolve. The fix is always:
php artisan icons:clear && php artisan icons:cache
Bake the clear into the deploy script ahead of the cache, unconditionally.
Silent "component not found" right after install. In order of likelihood:
php artisan view:clear
php artisan optimize:clear
composer dump-autoload
Package discovery caches metadata in bootstrap/cache/packages.php. If you installed with --no-scripts, or that cache predates the install, the provider never registers — and Laravel does not error loudly on an unknown x-unknown-component; it renders an empty string. If optimize:clear doesn't fix it, confirm registration directly:
cat bootstrap/cache/packages.php | grep gravity
Prefix collisions with your own components. If resources/views/components/ contains a file that shadows gravity-ui-icon-alarm, the app component wins and you get a confusing render with missing classes or wrong paths. grep -r "gravity-ui-icon" resources/views/ will find offenders. Publishing the config and renaming the prefix is cheaper than auditing every shadowed name.
Accessibility is your responsibility — the component adds no ARIA. Two cases, and they're different:
{{-- Decorative: hide from assistive tech --}}
<x-gravity-ui-icon-plus class="w-4 h-4" aria-hidden="true" focusable="false" />
{{-- Meaningful: label the BUTTON, hide the ICON --}}
<button type="button" aria-label="Delete record">
<x-gravity-ui-icon-trash-bin class="w-4 h-4" aria-hidden="true" />
</button>
Note the pattern: the label goes on the interactive element, the icon gets aria-hidden. Don't put role="img" + aria-label on an icon inside an already-labelled button — screen readers will announce it twice. focusable="false" is legacy IE/Edge insurance; harmless to keep.
Watch the vendor risk, and note how cheap the mitigation is. Zero stars, zero dependents, single maintainer, largely automated release cadence. The failure mode is benign — MIT licence, and the payload is just SVG files — but it's still a solo-maintained package. The entire dependency is resources/svg/ plus a ~50-line service provider. If this becomes load-bearing:
vendor/codeat3/blade-gravity-ui-icons/resources/svg/ to resources/svg/gravity-ui/.public function boot(): void
{
BladeIcons::registerIcon('gravity-ui-*', resource_path('svg/gravity-ui'));
}
composer remove codeat3/blade-gravity-ui-icons.Ten minutes of work, and the dependency disappears. Do it before you discover a missing icon on a Friday, not after. Bonus: adding a new icon afterwards is a cp from the upstream repo, with no Composer release cadence in the loop.
Extension point, with a caveat. The package's icon class extends BladeUI\Icons\Svg, which is the seam for global post-processing — stripping hardcoded width/height, injecting aria-hidden, rewriting currentColor. But a custom icon class means losing the benefit of icons:cache, which was the whole performance argument. Prefer doing transformations in your wrapper component (Pattern 1): same seam, none of the cache penalty.
Pin to a minor. With 1.4.0 landing Laravel 13 compatibility, the package tracks framework majors, which is good. But the release cadence is largely automated, and the diff between a working and broken icon component is often just a regenerated SVG directory. "codeat3/blade-gravity-ui-icons": "^1.4" and let the lock file do its job — don't chase patch releases unless you need a specific icon.
How can I help you explore Laravel packages today?