codeat3/blade-grommet-icons
Easily add Grommet icons to Laravel Blade views with codeat3/blade-grommet-icons. This lightweight package provides a simple Blade component for rendering any Grommet icon, with support for custom sizing, colors, and extra attributes for flexible UI integration.
Install and first render (2 minutes).
composer require codeat3/blade-grommet-icons
Auto-discovery registers the service provider — no config/app.php edit, no migrations, no assets to publish, no npm step. If you're on a cached config in a running environment:
php artisan package:discover --ansi
php artisan view:clear
Render your first icon directly in any Blade view:
<x-grommet-icon name="add" />
<x-grommet-icon name="checkmark" color="#0f766e" size="20" />
Where to look first: the SVG directory, not the docs. The authoritative list of valid name values is the shipped icon set:
ls vendor/codeat3/blade-grommet-icons/resources/svg | head -50
ls vendor/codeat3/blade-grommet-icons/resources/svg | wc -l # total coverage
The name you pass is the filename without .svg. Before you commit to this package, run that wc -l and grep for the specific icons your mockups use (trash, search, form-next, status-warning...). This is a five-minute check that prevents the single most expensive failure mode: discovering a vocabulary gap halfway through a template migration.
First real use case. Don't start with a greenfield page. Pick one existing view that already has hand-pasted inline SVG or a Font Awesome <i> tag, and replace exactly one icon:
{{-- before --}}
<i class="fa fa-plus"></i>
{{-- after --}}
<x-grommet-icon name="add" class="w-4 h-4" aria-hidden="true" />
Compare the compiled HTML in the browser inspector. Confirm the SVG inherits colour from the parent (stroke="currentColor" / fill behaviour varies by icon) before you scale out. Then move on to a wrapper component.
Pattern 1: A single project-level wrapper, not raw tags everywhere. The raw component is verbose and leaves sizing/accessibility decisions to every call site. Wrap it once:
{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'size' => 16, 'label' => null])
<x-grommet-icon
:name="$name"
:size="$size"
:aria-hidden="$label ? null : 'true'"
:aria-label="$label"
role="{{ $label ? 'img' : null }}"
{{ $attributes->merge(['class' => 'inline-block shrink-0 align-[-0.125em]']) }}
/>
Call sites become <x-icon name="trash" class="text-red-600" />, and a future swap to Heroicons or Tabler is a one-file change plus a name-mapping array — not a repo-wide sed.
Pattern 2: Semantic aliases for a design system. Grommet names describe glyphs, not intent. If your design tokens say icon-danger, map it:
// config/icons.php
return [
'danger' => 'status-critical',
'success' => 'status-good',
'pending' => 'status-warning',
'delete' => 'trash',
'edit' => 'edit',
'next' => 'form-next',
];
<x-icon :name="config('icons.danger')" class="text-red-600" />
This is what lets design review say "we use danger, not status-critical" and keeps a vocabulary change a config diff.
Pattern 3: Dynamic names — always whitelist. A name bound from a DB column or user input is a template-injection-ish footgun (worst case it renders nothing; it never executes code, but you can leak the existence of a path). Constrain it:
@php
$allowed = ['add', 'checkmark', 'close', 'search', 'trash'];
$icon = in_array($row->icon, $allowed, true) ? $row->icon : 'status-unknown';
@endphp
<x-icon :name="$icon" size="18" aria-hidden="true" />
Better: put the whitelist in config and validate at write time in a FormRequest.
Pattern 4: Migration-by-mapping-table. For a strangler replacement, build the map as data, not as a document:
// old reference => grommet name
return [
'fa-plus' => 'add',
'fa-check' => 'checkmark',
'fa-times' => 'close',
'fa-magnifying-glass' => 'search',
'fa-trash' => 'trash',
];
Then work view by view, and keep a grep -rc 'fa-' resources/views number in your PR description so the burn-down is visible.
Pattern 5: Email and PDF renderers. These are the highest-value targets because icon fonts and JS sprite injection simply don't work there. Inline SVG from Blade renders reliably in Mailables and in DomPDF/Snappy output. Caveat: some email clients strip or mangle inline <svg>. If you're targeting Outlook desktop, either accept the known degradation or export PNGs — do not assume parity.
Pattern 6: Deploy caching. blade-ui-kit/blade-icons supports an icon cache. If you enable it in CI/CD, the cache must be built at deploy time on a writable filesystem and read at runtime on a read-only one:
# in your deploy script, after composer install
php artisan blade-icons:cache
Pair it with php artisan blade-icons:clear in your rollback path. Verify the exact command names in php artisan list | grep blade — the wrapper package doesn't define them, blade-icons does.
Pattern 7: A test that renders every icon you use. Because a bad name fails silently, add a cheap guard:
it('renders every registered icon name used in views', function () {
foreach (config('icons') as $alias => $grommetName) {
$this->blade("<x-icon name=\"{$grommetName}\" />")
->assertSee('<svg', false);
}
});
A stronger version greps resources/views for name="..." and asserts the corresponding file exists in the vendor SVG directory — that catches an upstream rename in CI rather than in a customer's browser.
Pattern 8: Coexistence during rollout. blade-icons prefixes components per set, so blade-heroicons (<x-heroicon-o-*>), blade-tabler-icons, and this package can coexist in one codebase while you migrate. They do not collide. Take advantage of that — migrate incrementally rather than in one heroic PR.
The silent-failure trap is the big one. A misspelled name (checkmark vs check-mark, form-next vs formnext) renders an empty string or, worse, the literal component markup. There is no exception, no log entry. In development, add a @if guard in your wrapper or a View::composer assertion that the SVG file exists; in CI, use the render test above. Grommet's names are not consistently hyphenated — read them off the directory listing, don't guess.
Naming is the contract. Grommet slugs are kebab-case renderings of the upstream names, and the mapping is not always obvious (StatusCritical → status-critical is fine; FormNext → form-next; but compound names like UserSettings will surprise you). Pin the version and treat an icon rename in a patch release as a breaking change for you, regardless of what semver says. Nobody will file that bug for you — there are zero dependents.
size vs class fight each other. If the component emits width="16" height="16" from the size prop, a Tailwind w-4 h-4 class may not win depending on specificity and where the attributes land on the <svg>. Pick one convention — I'd pass size and skip utility classes for dimensions, or drop size entirely and let Tailwind own it. Mixing produces icons that are subtly the wrong size on one page and you'll spend an hour on it.
Colour inheritance depends on the glyph. Grommet icons are largely stroke-based, so stroke="currentColor" and text colour work. Some are filled. If color="..." and a parent text-* class disagree, you'll get a half-recoloured icon — check the raw SVG for fill vs stroke before debugging your CSS.
Don't inline the same icon 200 times in a long list. Each render is inline SVG markup — it's cheap but not free. For a table with 50 rows × 3 action icons, consider rendering once into a <symbol> sprite in your layout and <use>-ing it, or at minimum measure page weight during the spike. The blade-icons cache helps compile-time, not output size.
ID collisions. If any Grommet icon contains <defs> with fixed ids (gradients, masks, clip paths), rendering it twice on a page produces duplicate IDs. Browsers tolerate it, but <use> references and some CSS selectors will misbehave. Grep the SVG directory for id=" to see if this is a live concern for the specific icons you use.
Accessibility is your job, not the package's. The component does not know whether an icon is decorative or meaningful. Decorative → aria-hidden="true" and no label. Meaningful → role="img" + aria-label (or a visually-hidden span). Icon-only buttons additionally need an accessible name on the <button>, not just the icon. Bake this into the wrapper component so it's decided once.
Version pinning and the transitive constraint audit. The package has essentially no adoption, so nobody else will catch a bad constraint range for you. Pin ^1.11 and commit the lockfile. Then prove it isn't your upgrade blocker:
composer why-not laravel/framework 13.*
composer show codeat3/blade-grommet-icons --tree
1.11.0 is a Laravel 13 compatibility bump with no features — but "this package supports Laravel 13" does not mean blade-icons or illuminate/support ranges resolve cleanly against your other dependencies. Test on a scratch branch.
Bus factor mitigation: know your exit. It's a single maintainer over a large family of codeat3/blade-* packages. The escape hatch is trivial and worth documenting in your ADR: copy resources/svg/ into resources/icons/grommet/, register it yourself with blade-ui-kit/blade-icons, and drop the Composer line. Half a day, no template changes, because the component tag stays the same if you keep your wrapper.
The set is likely frozen at import. Don't expect new Grommet icons to appear between releases. If upstream Grommet adds glyphs you need, you're either vendoring or waiting indefinitely — check the release history for whether the SVG directory ever changes versus only composer.json constraints moving.
Confirm upstream licensing before shipping a white-labelled or client-delivered build. The Composer package is MIT, but that covers the wrapper, not necessarily the Grommet icon artwork it redistributes. If you hand source or built artifacts to a client, verify attribution requirements against the upstream Grommet project rather than assuming MIT covers everything.
Debugging checklist when an icon "doesn't show". In order: (1) php artisan view:clear — stale compiled Blade is the #1 cause after an upgrade; (2) confirm the file exists in vendor/.../resources/svg/ with the exact name; (3) inspect the rendered HTML — if there's no <svg> at all, the component isn't resolving (check php artisan package:discover, and that you didn't typo x-grommet-icon); (4) if <svg> exists but is invisible, check stroke/fill and whether an ancestor has display:none or zero width; (5) if it's broken only in production, suspect a stale blade-icons cache built before your latest icon was added — rebuild it in the deploy pipeline.
How can I help you explore Laravel packages today?