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

Icon Selector Bundle Laravel Package

nowo-tech/icon-selector-bundle

nowo-tech/icon-selector-bundle adds an icon picker to Symfony forms, letting users browse and select icons from configured icon sets and store the chosen icon on your entity. It ships a ready-made form type and selector UI with simple configuration for your icon library.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the bundle alongside Symfony UX Icons — the bundle depends on it for the icon catalogue and rendering:

composer require nowo-tech/icon-selector-bundle symfony/ux-icons

Add the icon sets you actually want to expose (the bundle does not ship icons; it proxies UX Icons):

php bin/console ux:icons:add heroicons
php bin/console ux:icons:add bootstrap-icons

Then wire the bundle's configuration so the form type knows which sets to render:

# config/packages/nowo_icon_selector.yaml
nowo_icon_selector:
    icon_sets:
        - heroicons
        - bootstrap-icons

If your app is on Symfony Flex, config/bundles.php should already contain Nowo\IconSelectorBundle\IconSelectorBundle::class => ['all' => true]; if not, register it manually (after Symfony\UX\Icons\UXIconsBundle).

First use case — the smallest thing that gives value: drop the type into an existing form and store the returned string on a nullable column.

use Nowo\IconSelectorBundle\Form\Type\IconSelectorType;

$builder->add('icon', IconSelectorType::class, [
    'label' => 'Record icon',
    'required' => false,
]);
#[ORM\Column(length: 255, nullable: true)]
private ?string $icon = null;

Render it, and preview it with the UX Icons Twig helper, which already understands the set:variant:name string the field produces:

{{ form_row(form.icon) }}

{% if record.icon %}
    <span class="record-icon">{{ ux_icon(record.icon, {class: 'w-5 h-5'}) }}</span>
{% endif %}

Where to look first in the repo — in order:

  1. README.md — for the exact config keys (icon_sets shape, per-set options, whether variants like heroicons:outline are addressable separately).
  2. src/Form/Type/ — the actual form type class and its configureOptions block; this is the contract you call from your forms.
  3. src/DependencyInjection/ — the extension that turns nowo_icon_selector.* YAML into services; that is the authoritative list of config keys.
  4. src/Resources/assets/ — where the selector grid and search input get initialised (Vite entrypoint). Do not read src/Resources/public/build.

Implementation Patterns

1. Field-level mode selection (direct grid only). When you know the user is picking from a curated shortlist, force the grid:

$builder->add('icon', IconSelectorType::class, [
    'icon_sets' => ['heroicons'],
    'mode' => 'select',
]);

Typical for an admin "category icon" picker where a handful of options cover 90% of needs.

2. Search-first mode for large catalogues. For an end-user field exposed to hundreds of Heroicons/Lucide glyphs, default to search so the form does not download every SVG:

$builder->add('icon', IconSelectorType::class, [
    'mode' => 'search',
    'icon_sets' => ['heroicons', 'bi'],
]);

Prefer this on anything user-facing; the grid is great for curated pickers and painful for wide sets on mobile.

3. Entity + form + Twig triple. Store the raw string, validate against the union of configured sets, and always render with ux_icon():

class MenuItem
{
    #[ORM\Column(length: 255, nullable: true)]
    #[Assert\Regex(
        pattern: '/^(heroicons|bi):[a-z0-9:-]+$/',
        message: 'Unsupported icon reference.',
    )]
    private ?string $icon = null;
}

The value the widget returns is just a string — treat it as untrusted input and enforce a prefix allowlist, both in validation and via a form option if the bundle exposes one.

4. Collection of icons. Combine with CollectionType when a record has several icon slots (e.g. sidebar + badge):

$builder->add('icons', CollectionType::class, [
    'entry_type' => IconSelectorType::class,
    'entry_options' => ['mode' => 'search'],
    'allow_add' => true,
    'allow_delete' => true,
]);

On Turbo/Live Components pages, ensure the collection add/remove re-triggers the widget's init (see the tips below) — the grid and search input are JS-backed.

5. Integration with Symfony UX Icons helpers. The stored value is intentionally compatible with ux_icon(), so do not wrap it in a custom Twig function. Use the string as-is:

{{ ux_icon(menuItem.icon ?? 'heroicons:outline:question-mark-circle') }}

For fallbacks, keep the default in Twig, not in the entity, so a null column stays meaningful.

6. Testing. The type is a normal AbstractType, so a form test is the fastest way to lock behaviour:

$form = $this->factory->create(IconSelectorType::class, null, [
    'icon_sets' => ['heroicons'],
]);
$this->assertArrayHasKey('icon', $form->createView()->children);

For submission, feed the string ('heroicons:outline:home') into the submit() payload; there is no model transformation, so getData() returns the string verbatim.

Gotchas and Tips

Icon sets must exist at container compile time. The bundle's DI extension likely resolves configured sets against UX Icons during boot. Adding ux:icons:add lucide after the fact without updating nowo_icon_selector.icon_sets means the grid simply never shows them — no runtime error, just silent gaps. After changing either side, run bin/console cache:clear and confirm the frontend container sees them.

ux:icons:add vs the bundle config is a two-place edit. UX Icons installs the set and the bundle exposes it. Forgetting one side is the most common onboarding mistake. A quick sanity check:

bin/console debug:config nowo_icon_selector
bin/console ux:icons:list  # or the equivalent in your ux-icons version

Never touch src/Resources/public/build. The bundle ships a Vite-built asset; local changes belong in src/Resources/assets/. If you patch behaviour in the built JS it will be overwritten on the next composer update. If you need a different icon grid, subclass the form type or override the Twig template via the bundle override convention (templates/bundles/IconSelectorBundle/...) instead.

Turbo and Live Components re-render. The selector boots its JS on DOMContentLoaded; a Turbo frame replace or a Live Component re-render drops that binding. Re-initialise on turbo:load and live:component:render in your app's JS entrypoint — usually a document.querySelectorAll('[data-icon-selector]') → init loop, or a small controller that observes data-live-render.

Prefix format is ambiguous for variants. heroicons:outline:home is set + variant + name, while bi:house is set + name. If you validate with a regex, allow the extra segment; if you parse for your own storage logic, split on : from the left and treat everything after the set as the icon identifier.

AssetMapper vs Webpack Encore. If the host app uses AssetMapper (default in Symfony 7+), confirm the bundle's Resources/public/build is registered via importmap.php (or that assets config picks up nowo-icon-selector-bundle). With Encore, you typically need to publish/import the built CSS/JS manually — otherwise you get an unstyled empty box with no errors in the console.

Don't trust the stored string on read either. ux_icon($string) will render <svg> for any parseable reference — an attacker with write access to the DB (or a mass-assignment path) can inject an unexpected icon or, if a future UX Icons feature supports @ paths, worse. Constrain by prefix on write and when rendering (either by a TwigExtension wrapper that whitelists, or by validating before ux_icon).

Public extension points to preserve if you fork/patch. The bundle is a Symfony bundle, so the safe extension surfaces are: the form type class (subclass it), the nowo_icon_selector.* config keys, and the Twig block structure of the form theme. Avoid renaming any of these in a local fork — future upstream updates will break, and dependents (currently zero, but growing) rely on them.

Version skew with UX Icons. The bundle targets PHP ≥8.1 and Symfony 6/7/8. Pin symfony/ux-icons to a range that matches your Symfony version; the icon rendering API has shifted historically. If ux_icon() returns a <span> placeholder instead of an <svg>, the icon set is not registered — check config/packages/ux_icons.yaml and the icon_sets value in nowo_icon_selector.yaml before assuming the bundle is broken.

Debugging. If the grid renders empty but the search works, the sets list is resolved but the icon enumeration is not — usually a UX Icons cache issue. bin/console ux:icons:add <set> --force (or cache:clear on the ux-icons cache) fixes most of these. If the field renders nothing at all, check that the form theme from the bundle is registered: twig.form_themes in config/packages/twig.yaml should include @IconSelector/form/... (the bundle usually adds it automatically via PrependExtensionInterface, but a host app's explicit form_themes list can shadow it).

Release cadence is sparse (last release noted 2026-10-25, zero dependents). Treat the CHANGELOG as the source of truth for breaking changes, keep a pinned constraint such as ^1.0 rather than *, and add one integration test of your own form so an upstream refactor fails loudly in CI rather than silently in production.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource