nickperkins/blade-icon-picker
A standalone icon picker Blade component for Livewire forms that works with any blade-icons pack, featuring search, lazy rendering, keyboard navigation, and responsive styling with no Tailwind dependency.
Install (5 minutes). Two Composer packages and one publish command:
composer require nickperkins/blade-icon-picker
composer require blade-ui-kit/blade-heroicons # or any blade-icons pack you already have
php artisan vendor:publish --tag=icon-picker-assets
Then two tags in your layout — CSS in <head>, JS at the end of <body> after Alpine:
<link rel="stylesheet" href="{{ asset('vendor/icon-picker/icon-picker.css') }}">
...
<script src="{{ asset('vendor/icon-picker/icon-picker.js') }}"></script>
Livewire 3 auto-registers the Alpine component; no Alpine.data() call is needed. Confirm the two published files actually exist in public/vendor/icon-picker/ before debugging anything else — a missing publish is the most common "nothing renders" cause.
Where to look first. The README's Usage → Basic block is the whole contract: wire:model for write-back, :value for initial/redisplay. Everything else (placeholder, disabled, class) is a thin prop. The theming section lists the CSS custom properties you can override; the dark-mode section tells you how to activate the dark palette. If you plan to fork, agents/AGENTS.md maps the repo: src/ is the service provider/icon manager/Blade component, resources/ holds the view, Alpine JS, and CSS, resources/dist/ is the built bundle that gets published.
First use case. Drop one picker into an existing Livewire form and render the chosen icon right next to it so you can see the round-trip:
class CreateMenu extends Component
{
public string $icon = '';
public function rules(): array
{
return ['icon' => ['required', 'string']];
}
}
<form wire:submit="save">
<x-icon-picker::icon-picker wire:model="icon" :value="$icon" placeholder="Choose a menu icon" />
@error('icon') <p class="text-red-500">{{ $message }}</p> @enderror
@if ($icon)<x-icon name="{{ $icon }}" class="size-6" />@endif
<button type="submit">Save</button>
</form>
The persisted string is a blade-icons ID (heroicon-o-home, fa-solid-bell), so it is immediately usable with <x-icon>, @svg(), or svg() in list and show views. That interop is the reason this is a low-regret adoption: swapping the picker later is a UI change, not a data migration.
Wrap the vendor tag once. Do not let product developers call <x-icon-picker::icon-picker> directly. Create <x-app.icon-field> that hard-enforces the dual binding, the error display, the theme class, and your standard :value="old(...)"-style fallback. This single step removes the entire class of "picker silently forgot the saved icon" bugs and lets you swap the implementation later without touching call sites.
Selection vs. binding. This is a Blade component with an Alpine island, not a Livewire component — do not reach for wire:model.live or assume per-keystroke server round-trips. Search, filtering, and lazy chunking happen in the browser; the only server interaction is the model write on selection. Design forms accordingly: validation runs on submit, not on icon hover.
Display outside the form. Because the value is a blade-icons ID, "show the chosen icon" is a one-liner anywhere in the app (<x-icon :name="$menu->icon" />). Wrap that in a small helper/partial that falls back to a placeholder when the string is empty — you will need this in tables and show pages.
Multiple pickers on one page. Give each an unambiguous wire:model key (icon, favicon, badge_icon). Early docs describe the catalog as serialized per page; v1.1.0 introduced AJAX-based loading with set/variant filtering — either way, verify the real cost on a page that hosts several pickers before shipping.
Search-aware labelling. Search is token-substring matching: ar le finds Arrow Left. This rewards consistent pack naming (arrow-left, arrow-right) and punishes abbreviations buried mid-word. If your UX copy promises a search, keep icon set names conventional.
Dark mode / theming. Two supported routes: add ip-dark to the component (or any ancestor) via class, or rely on a [data-theme="dark"] attribute upstream. Bind it to your existing theme store rather than inventing a second toggle. For brand alignment, override the documented custom properties in your own stylesheet scoped to the picker root — do not edit published vendor CSS, because the next vendor:publish overwrite (or a fresh deploy) will wipe it.
Validation. required|string is the minimum. If you want to prevent arbitrary strings, validate against the actual icon catalog rather than trusting the client — the component constrains the UI, not the request payload.
Deployment. Commit the published assets (or run the publish step in your build pipeline). v1.0.2 shipped precisely because missing dist assets broke vendor:publish after install, so treat the published directory as part of your deploy artifact and cache-bust it alongside the rest of your vendor assets.
Pinning and ownership. Pin an exact version, keep a fork-ready note, and budget for dependency bumps when Laravel majors move (the README's window is Laravel 10–12; v1.0.0's release notes say 10–13 — resolve that against your composer.json before upgrading).
wire:model="icon" alone does not seed the initial value. Omit :value="$icon" and the picker renders blank on an edit form — silently, with no console error. The wrapper partial is the durable fix; a PR-checklist rule is the cheap one.prefers-color-scheme and then states it is supported automatically; theming examples reference .icon-picker-root while the dark-mode section uses .ip-root. Open devtools, find the real root class, and write your override against that. Don't copy-paste the docs examples blind.:value holding an unregistered ID is not documented behaviour.unsafe-inline or a nonce. If your CSP is strict, dry-run the picker on a staging page early; x-html/innerHTML insertion is the usual failure point.dist assets). After any re-publish, re-check the two files — a stale public/vendor/icon-picker/icon-picker.css will make your theming overrides appear to "not work".How can I help you explore Laravel packages today?