silarhi/ux-icons-js-bundle
Symfony bundle that plugs Iconify's JavaScript runtime into Symfony UX Icons, letting you render any of Iconify's 200,000+ icons on demand—no per-icon SVG imports or manual icon sets—via a single lightweight script that loads icons at runtime in your Twig templates.
symfony/ux-icons is the icon tooling from Symfony UX: it scans icon directories, dumps them into an optimized sprite/per-icon module output under var/icons/, and gives you a thin JS runtime (icon()-style rendering, plus loadIcon/UXLazyIcon as of v0.2.0). There is no Laravel service provider — you're adopting the JS runtime plus a console command, not a package that registers itself.
Minimal path:
composer require symfony/ux-icons --dev — you mainly want the CLI dumper and the IconSet/Iconify on-demand fetcher; the browser side is plain ESM.app/Console/Commands/DumpIconsCommand.php (or shell out to it in a Composer post-autoload-dump script) and point its output at resources/icons/ or public/build/icons/ instead of var/icons/.heroicons, lucide, iconify prefix). Register prefixes so heroicon:user and lucide:user don't collide.{{-- resources/views/components/icon.blade.php --}}
@props(['name', 'size' => 24])
<svg {{ $attributes->merge(['width' => $size, 'height' => $size]) }} aria-hidden="true">
<use href="{{ asset('build/icons/sprite.svg') }}#{{ $name }}"></use>
</svg>
First real use case: deleting a folder of hand-pasted Heroicons and replacing every <x-icon name="..."/> with one dumped set, so icons are tree-shaken and versioned together with the rest of your frontend.
Where to look first: the generated manifest (var/icons/*.php or .json) to confirm what actually got dumped, and var/icons/loaders.js — that file is the contract for the new runtime-name feature.
Build-time dump, request-time render. Treat the dump as a build artifact, not a runtime concern. Wire it into npm run build or a post-autoload-dump Composer script so php artisan icons:dump runs before Vite compiles. Never resolve icons per-request from disk in production.
Sprite for static names, per-icon modules for dynamic names. For icons you know at compile time, the sprite + <use> (or an inlined <svg>) is cheapest. For names that only exist at runtime — a CMS "icon" field, an API payload, a user-chosen emoji set — v0.2.0 gives you the second lane:
import loaders from '../../var/icons/loaders.js';
import { loadIcon } from 'symfony/ux-icons'; // adjust to the package's JS entrypoint
async function renderDynamicIcon(name, el) {
try {
el.innerHTML = await loadIcon(loaders, name);
} catch {
el.innerHTML = await loadIcon(loaders, 'fallback-question');
}
}
Each icon is a separate module, so the network request is one file — you're not shipping the whole set to render one glyph.
Framework components for component-driven frontends. If you're on Inertia + Vue or React, UXLazyIcon is the idiomatic wrapper: it takes name, fetches the module on mount, and renders a fallback prop/slot until it resolves.
<UXLazyIcon name="heroicon:bell" class="size-4">
<template #fallback><span class="size-4 animate-pulse rounded bg-gray-200" /></template>
</UXLazyIcon>
Resolve var/icons in Vite. Laravel's vite.config.js doesn't know about the generated path. Add an alias so imports stay readable and survive the directory move out of Symfony's convention:
resolve: { alias: { '~icons': path.resolve(__dirname, 'resources/icons/generated') } }
Cache resolved SVG strings when the name comes from user data — Cache::remember("icon:{$name}", now()->addDay(), fn () => ...). The loader map is cheap, but you don't want a per-render module resolution on a listing page with 200 rows.
Custom icon sets as an extension point. If dumping is impractical (huge Iconify catalog), implement the package's icon-set interface against your own fetcher and let the on-demand path hit your CDN/cache instead of a local directory. That's the escape hatch when the dump gets slow.
composer require and go.var/ is a Symfony convention and is usually gitignored. In Laravel you almost certainly want output under resources/ or public/build/; if you keep var/, make sure CI regenerates it, because a fresh clone has no manifest and every icon silently renders empty.<use href="...#icon-foo"> with no console error. When an icon "disappears," diff the manifest against the directory first.heroicon:user vs user), case, and kebab-casing all have to match between the dump, the sprite ID, and the name you pass at runtime. Unknown names in loadIcon reject — always try/catch and fall back, especially for API-supplied names (treat them as untrusted input; don't interpolate into HTML unescaped).width/height or a Tailwind size-* class so the box doesn't collapse while the module downloads.UXLazyIcon for below-the-fold or truly dynamic slots.loaders.js? (2) does the sprite contain #icon-<name>? (3) does the network tab show a request for the per-icon module (404 → path/alias wrong)? (4) is Vite's build.rollupOptions externalizing the generated dir by accident?loaders.js is a reference map, not the SVGs. Importing it is cheap and doesn't inline every icon body, but it does pin every icon name into the bundle graph — regenerating it invalidates chunks, so keep the dump in the same commit as the icon changes to avoid noisy hashes.^0.2 is fine only if you're watching release notes — the loadIcon/UXLazyIcon surface is brand new in this release and will likely move before 1.0.How can I help you explore Laravel packages today?