Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ux Icons Laravel Package

symfony/ux-icons

Render local and remote SVG icons in Twig with the ux_icon() function or the twig:ux:icon component. Pull icons from any registered pack, like mdi:check, and style them inline with classes. Browse the full icon set and live demo at ux.symfony.com/icons.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: symfony/ux-icons is a Symfony bundle, not a Laravel package. There is no laravel-ux-icons, no service provider auto-discovery, no Flex recipe. What you actually get when you composer require symfony/ux-icons in a Laravel app is the library half: IconRenderer, IconRegistry, the Iconify and local-SVG factories, the sanitizer, and a Twig extension (UXIconExtension) that exposes ux_icon(). You wire it yourself. That is 30–60 lines in a service provider, and it is entirely doable — but budget for it.

Minimal path:

  1. composer require symfony/ux-icons twig/twig (Twig is a hard dependency for the template-facing part; skip it only if you're calling the renderer from Blade directly).
  2. Create config/ux_icons.php returning the same array shape Symfony uses (icon_dir, default_icon_set, icon_sets, aliases, iconify). The bundle's YAML keys map 1:1 onto the array the factories and registry consume.
  3. In a service provider, build new IconRegistry(...), register LocalSvgIconFactory pointing at resource_path('icons'), register IconifyIconFactory if you want the remote long tail, wrap both in IconRenderer, then $twig->addExtension(new UXIconExtension($renderer, ...)).
  4. First use case: {{ ux_icon('mdi:check', {class: 'w-4 h-4'}) }} in a Twig-rendered view. Start with a local icon (drop an SVG into resources/icons/check.svg, call ux_icon('check')) before touching Iconify — it isolates wiring bugs from network bugs.

Where to look first, in order: IconRenderer::renderIcon(string $name, array $attributes): string (that's the whole API surface you'll use 95% of the time), IconRegistry (name → factory resolution, prefix parsing, aliases), then the config array. The <twig:ux:icon name="…" /> component (added in 2.17) only helps if you're running Twig with the Symfony UX Twig Components runtime — in a plain Laravel+Twig setup, skip it and use the function.

If your app is Blade-only and unlikely to change: stop here. Use blade-ui-kit/blade-icons instead; it solves the same problem idiomatically and you won't be maintaining a bridge. This package earns its keep when Twig already exists — transactional emails, PDF/invoice templates, API-rendered HTML fragments — or when you're deliberately moving to a hybrid template stack.

Implementation Patterns

Blade bridge, if you're hybrid. Don't fight it — expose one Blade component that delegates:

// app/View/Components/Icon.php
public function render() { return new HtmlString($this->renderer->renderIcon($this->name, $this->attrs)); }

Then <x-icon name="mdi:check" class="w-4 h-4" />. HtmlString bypasses Blade escaping; that's safe because the renderer sanitizes (see Gotchas), but only pass icon names you control — see the allowlist pattern below.

Local-first, remote for the long tail. Put brand marks and anything legally/semantically yours in resources/icons/ (committed, reviewed, greppable). Point iconify at the public API only for generic UI glyphs (mdi:, heroicons:). This keeps your critical path offline and makes code review meaningful — a changed SVG shows up as a diff.

Lock iconify icons into the repo. The package supports "locking on-demand icons" (the Symfony bundle ships it as a console command; wrap the same logic in an php artisan icons:lock command). Do this in CI and fail the build if it produces a diff. This is the single highest-value operational pattern: no production traffic depends on api.iconify.design being up, deploys are reproducible, and the sanitizer runs at lock time rather than at request time. iconify.auto_lock (3.4+) sounds convenient but writes files at runtime — useless on read-only container filesystems and a race under concurrency.

Config as a registry, not a magic string. Use icon_sets (2.20+) to give each directory its own prefix (heroicon:, brand:, mdi:) and aliases (2.20+) for semantic names (icon: 'check' → 'mdi:check'). Aliases pay off immediately: designers change icon packs, your templates don't change.

Validate names against the registry, not a regex. Any icon name that originates from user input or the database should be checked against IconRegistry::has() (or a config-driven allowlist) before rendering. This kills two problems at once: XSS surface and "attacker makes your server fetch arbitrary remote URLs." A Blade directive reading from a config('icons.allowed') map is a fine implementation.

Cache where it counts. v3.6.0's wins — rendered-HTML cache, one cache fetch per icon per request — are Symfony cache-pool features. In Laravel, do the cheap version: make IconRenderer a singleton and memoize name+attrs → html in a private array for the request. That alone covers nav bars, tables, and icon-heavy grids (the same 15 icons rendered 200 times). Then, for cold-start predictability, mirror cache:warmup with a deploy-step Artisan command that renders every icon in every icon set once and writes results to Cache::store('file') or a static manifest — this is what replaces the Symfony warmup hook you don't have.

Dynamic and conditional icons. {{ ux_icon(user.isAdmin ? 'shield' : 'user') }} works because rendering is server-side — no hydration, no JS. Combine with currentColor and Tailwind (class: 'text-green-500 w-5 h-5') for state-driven colour. Suffix support (2.33+) lets you express variants off one base name; use it for size/weight variants rather than minting a new alias per combination.

Attribute passing. Since 2.20, <twig:ux:icon> accepts int/float attributes (stroke-width: 1.5). Always pass aria-hidden="true" alongside visible text labels, and role="img" + a <title> (or aria-label) for standalone meaningful icons — the package renders the <svg> but doesn't make accessibility decisions for you.

Testing. Assert on something stable you control — add data-icon="check" via the attributes array and assert that — rather than on full SVG markup. Markup assertions broke for everyone in 2.24 (see Gotchas) and again with sanitization. For a snapshot approach, keep one golden-file test per icon set so an unexpected Iconify upstream change is visible in review.

Gotchas and Tips

Version/platform trap. 3.x requires PHP 8.4 and Symfony 7.4 — which means it drags Symfony 7.4 components into your vendor/ alongside Laravel's own Illuminate stack. That's usually fine (they coexist), but it's real weight and a real constraint: if you're on PHP 8.2/8.3, you're pinned to the 2.36.x line. Check composer why symfony/http-foundation after install to make sure nothing else in your tree gets upgraded as a side effect.

The CVE matters and it's recent. Iconify bodies and local files are sanitized as of 2.36.1 / 3.2.0 (strips <script>, <foreignObject>, event handlers, javascript:/data:text/html hrefs, CDATA). Two consequences: (a) if you're on anything older, upgrade — you're inlining third-party SVG into your HTML; (b) after upgrading, purge your icon cache / re-run your lock command, because already-cached icons were cached pre-sanitization. If you memoize into Laravel's cache, flush those keys too. The sanitizer keeps <style> (so light/dark theming via prefers-color-scheme still works) but strips SMIL animation elements — if you were relying on <animate>, it's gone.

xmlns broke everyone's tests. 2.24 added xmlns to downloaded icons so they render correctly as standalone files. Any test asserting on exact ux_icon() output broke. Lock your icons before upgrading and force-relock after, then fix assertions to target data-* attributes instead of raw markup.

ignore_not_found (2.19+) is a footgun. It silences the exception when an icon can't be resolved, so the page renders without it. Reasonable for user-facing pages where a missing glyph shouldn't 500; catastrophic as a global default, because it turns typos and broken lock steps into silent visual regressions. Enable per-render or per-environment, and add a CI check that every icon name used in templates resolves.

Remote fetch at render time = latency and failure modes. If you don't lock icons, each uncached icon is an HTTP round trip inside your request, with whatever timeout symfony/http-client gives you. That's a p99 landmine and a self-inflicted availability dependency. 2.25 improved the DX when symfony/http-client is absent — but "improved DX" means a clearer error, not a working setup; install it or stay local-only.

ID collisions when inlining. SVG ids (gradients, masks, clipPaths) are document-global. Render the same complex icon twice on one page and the second may reference the first's defs — usually fine, occasionally visually wrong, and near-impossible to debug. For icons with internal ids, prefer <img src="/icons/x.svg"> or an SVG sprite over the inline path.

class merges onto the root <svg> — it doesn't replace internals. If an icon has hardcoded fill="#000", your text-red-500 won't apply. Audit locked icons for hardcoded fills; prefer icon sets authored with currentColor. Similarly, Tailwind's JIT won't see classes generated server-side inside PHP arrays — keep icon classes literal in templates or safelist them.

PDF and email rendering. DomPDF/mPDF handle inline SVG inconsistently; ImageMagick/Rsvg may be absent. For invoices, either pre-render icons to PNG or accept that the icon may vanish in the PDF. For HTML email, inline SVG is unsupported in Outlook desktop and some Gmail contexts — use icons sparingly there and consider a text/CSS fallback.

Escaping. In Twig, ux_icon() returns safe markup — {{ }} is correct. In Blade you must use {!! !!} or HtmlString; the moment you pipe raw SVG through {{ }} you'll see escaped text and wonder why. Don't "fix" it by disabling escaping globally.

Debugging a blank/missing icon. Resolution order is: alias → explicit set prefix → default_icon_set. If ux_icon('check') renders nothing, it's almost always a missing default_icon_set or an icon_dir that's a relative path resolving against the wrong CWD. Verify the icon name exists at ux.symfony.com/icons first (Iconify names are not guessable — many packs use -outline/-solid suffixes), then check the locked directory.

Extension points. If your icons live somewhere dynamic (database, S3, tenant-specific brand assets), implement IconFactoryInterface and register it — that's the sanctioned seam. Trying to hook the renderer's cache or the registry's internal map is a losing game; those are @internal-ish and shifted in 3.x.

Issues and support. This repo is a read-only subtree split of the symfony/ux monorepo — no PRs or issues here. Bug reports go to symfony/ux. Release cadence is tied to the monorepo, so a "last release" date reflects the umbrella project, not this package's churn. With ~56 stars on the split, community examples are thin; lean on the Symfony docs at symfony.com/bundles/ux-icons, which apply almost verbatim to the library half you're using.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log