raise-studio/filament-icon-picker

Self-developed, dependency-free Filament form field for picking Heroicons. Zero per-icon server roundtrips, modal grid, recents & favorites, keyboard-first, WCAG 2.1 AA.

Maintainers

Package info

github.com/raise-studio/filament-icon-picker

pkg:composer/raise-studio/filament-icon-picker

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-31 04:02 UTC

This package is auto-updated.

Last update: 2026-07-31 04:10:41 UTC


README

πŸ“˜ δΈ­ζ–‡ζ–‡ζ‘£οΌš readme.zh-CN.md

A self-built, zero extra front-end dependency Filament form icon-picker field component. One-shot SVG map delivery Β· modal grid Β· recents / favorites Β· keyboard-first Β· multi-icon-set extensibility Β· accessibility-friendly.

Provides a handy icon picker inside any Filament form: a styled input triggers a standalone modal with a large icon grid, live search, recents and favorites sections, style switching, and keyboard navigation, plus support for plugging in any third-party or custom icon set.

1. Overview

filament-icon-picker is a Filament form field component package that does exactly one thing well: provide icon-selection capability inside a form.

  • Zero extra front-end dependencies: Interaction logic is built on Alpine (bundled with Livewire); assets are registered through FilamentAsset β€” no npm package required.
  • Performance-first: When the modal opens, the entire name β†’ inline-SVG map is fetched in a single request; all subsequent rendering, search, and style switching happen client-side with no per-icon server round-trips.
  • Multi-icon-set architecture: Ships with Heroicons (Outline / Solid styles) and natively supports plugging in any third-party / custom icon set side-by-side (e.g. Feather, Lucide, or your own SVGs).
  • Works out of the box: Auto-discovery of directory-based icon sets, localStorage-backed recents / favorites, light & dark theme adaptation.

2. Features

  • Modal grid picker: A popup independent of the input, a spacious responsive N-column grid β€” no more cramped dropdowns.
  • Live search: Filter as you type, with exact-prefix matches ranked first (150ms debounce by default).
  • Recents + favorites: Persisted to localStorage, pinned at the top, retained across sessions.
  • Style switching: When the current set supports multiple styles, switch (e.g. Outline / Solid) and preview live inside the modal.
  • Keyboard-first: ↑ ↓ ← β†’ move focus through the grid, Enter selects, Esc closes, typing jumps to the match.
  • Accessibility: Focus trap + ARIA dialog / tablist / grid semantics, WCAG 2.1 AA compliant.
  • One-click copy: Copy the full class name of the current icon to the clipboard.
  • Multi-icon-set: The icon-set switcher bar appears on demand, supporting Heroicons alongside any custom set.
  • Zero-cost extension: Drop a folder to plug in, register in one line of code, or implement a contract to take over dynamic / font / remote icons.
  • Safe inlining: Third-party SVGs are sanitized by the built-in SvgSanitizer before inlining β€” strips scripts and event-attribute risks.
  • Visual polish: Glassmorphic modal, magnetic hover, smooth transitions, automatically following the panel's light / dark theme.

3. Requirements

Dependency Version
PHP ^8.2
Laravel ^12 (required by Filament 4)
Filament ^4
spatie/laravel-package-tools ^1.16
  • Livewire and Alpine ship with Filament 4 β€” no separate installation required.
  • Built-in icon SVG resources are published with the package β€” no host-side icon component package needed.
  • Zero additional npm dependencies.

4. Installation

4.1 Install via Composer

composer require raise-studio/filament-icon-picker

The service provider is already declared in composer.json's extra.laravel.providers. After Composer installs it, it auto-registers β€” no manual plugin or service-provider wiring needed.

4.2 Publish front-end assets

JS / CSS are registered via FilamentAsset and must be published once into public/ to be loadable by the browser:

php artisan filament:assets

During development, if you use a path repository + symlink, you still need to re-run the command above after asset changes and hard-refresh the browser (Ctrl + F5).

4.3 Development (path repository, optional)

Add a path repository with symlink to the host composer.json:

{
    "require": {
        "raise-studio/filament-icon-picker": "^0.1.0",
        "php": "^8.2"
    },
    "repositories": [
        {
            "type": "path",
            "url": "../packages/icon-picker",
            "options": { "symlink": true }
        }
    ]
}

5. Basic Usage

Use the field class inside any Filament Schema / Form:

use RaiseStudio\IconPicker\Forms\Components\HeroiconPicker;

HeroiconPicker::make('icon')
    ->label('Icon')
    ->allowStyleToggle()   // allow Outline / Solid switching (on by default)
    ->showRecents()        // show recents (on by default)
    ->showFavorites()      // show favorites (on by default)
    ->placeholder('Select an icon');

The field extends Filament\Forms\Components\Field, so all common methods (label / placeholder / required / visible / helperText …) are available.

Storage convention: The field always saves a fully-qualified string. The built-in Heroicons set is saved as heroicon-o-xxx / heroicon-s-xxx; a custom icon set is saved in <set>:<style>:<name> format (see Chapter 7). Style switching only changes the o / s prefix or the within-set style β€” it never loses the full class name, making it easy for the rendering side to fetch the SVG by full name.

6. API Reference & Property List

6.1 Field configuration methods

HeroiconPicker provides the following methods on top of Field:

Method Default Description
->style(string $style) 'outline' Initial style: outline | solid (applies to built-in Heroicons only)
->allowStyleToggle(bool $v = true) true Whether to show the style switcher in the modal (only effective when the current set supports multiple styles)
->showRecents(bool $v = true) true Whether to show the "Recents" section
->showFavorites(bool $v = true) true Whether to show the "Favorites" section
->recentLimit(int $n) 12 Number of recents retained (minimum 1)
->gridColumns(int $n) 8 Modal grid columns (responsive, minimum 2)
->searchDebounce(int $ms) 150 Search debounce in ms (minimum 0)

6.2 Field state convention

  • The database column should be VARCHAR holding the full class-name string β€” no schema change, zero migrations.
  • Built-in set: heroicon-<style>-<name>, e.g. heroicon-o-document, heroicon-s-folder.
  • Custom set: <set>:<style>:<name>, e.g. feather:default:home, custom:default:star.

6.3 Consuming the selected icon on the render side

The component only produces / back-fills the class name; rendering is left to the consumer. Fetch the inline SVG by full name:

use RaiseStudio\IconPicker\Icons\IconManifestContract;

$svg = app(IconManifestContract::class)->svg($record->icon);
// $record->icon can be heroicon-o-document (built-in) or feather:default:home (custom set)

IconManifest::svg() automatically routes the full name to the correct icon set and returns the inline SVG; it returns null when not found.

7. Customizing Third-Party Icon Sets (key section)

The component natively supports plugging in any third-party / custom icon set alongside the built-in Heroicons. The "Icon Set" switcher bar at the top of the modal appears automatically as sets are registered; storage uniformly uses the set:style:name format, fully compatible with the built-in Heroicons data.

7.1 Core concepts & storage format

  • Set: An icon library identified by a unique key (e.g. heroicons / feather / custom), used as the routing and storage prefix.
  • Style: A variant dimension within a set. Heroicons uses outline / solid; a single-style set typically uses default.
  • Fully-qualified name: <set>:<style>:<name>, e.g. feather:default:home, brand:default:logo-a.
  • Built-in Heroicons keep the legacy heroicon-o-xxx format for backward compatibility; both can coexist without interference.

7.2 Method 1: Directory auto-discovery (zero code, recommended for pure SVG libraries)

IconManifest automatically scans the top-level subdirectories of the host application's resources/icons/ directory at construction time: each subdirectory is auto-registered as a DirectoryIconSet, and the modal switcher bar immediately shows that set β€” drop an SVG folder into your own project and it's plugged in, no PHP required.

⚠️ Only put extension icon libraries into your own application directory β€” never into the composer-installed vendor/.../filament-icon-picker/resources/icons/. That directory belongs to the package itself and gets overwritten by composer update, so any changes there are guaranteed to be lost.

The default scan path is the application resource_path('icons') (i.e. <your-app>/resources/icons/). It can be overridden via the icon-picker.discover_path config (absolute paths supported). This config ships in config/icon-picker.php; publish it with php artisan vendor:publish --tag=filament-icon-picker-config and edit as needed.

Directory convention (the directory structure is the configuration):

<your-app>/resources/icons/
β”œβ”€β”€ feather/                  ← auto-discovered: flat *.svg β†’ single style "default"
β”‚   └── home.svg  user.svg …
β”œβ”€β”€ bootstrap/                ← multi-style: subfolder name is the style
β”‚   β”œβ”€β”€ outline/*.svg
β”‚   └── solid/*.svg
└── <your-library>/           ← drop it in and it's plugged in
    └── *.svg (or split into styles via outline/ solid/ subfolders)

The built-in Heroicons SVGs live in the package's own resources/icons/ root (read by a dedicated class, not part of auto-discovery). This is package-owned resource β€” please do not modify or depend on its path.

Rules:

  • A directory containing *.svg directly (flat) β†’ single style, style name is default;
  • A directory that also contains subfolders (each holding *.svg) β†’ subfolder name is the style (e.g. outline / solid);
  • Optional meta.json to override display name / style list / default style:
    { "label": "Lucide", "styles": ["outline"], "defaultStyle": "outline" }
  • The directory name is auto-converted to a human-readable label (lucide β†’ Lucide, brand-icons β†’ Brand Icons).

Replace / override an existing set: Calling register() with the same key overrides the original set (see 7.3). For example, to replace the built-in Heroicons entirely, register your own DirectoryIconSet with key heroicons; do not override by dropping same-named files into the vendor directory.

7.3 Method 2: One-line registration (host-directory SVG library)

When the icon library lives in a host project directory (rather than inside the package), use DirectoryIconSet to register it in one line inside the host AppServiceProvider::boot():

use RaiseStudio\IconPicker\Icons\DirectoryIconSet;
use RaiseStudio\IconPicker\Icons\IconManifestContract;

public function boot(): void
{
    $manifest = app(IconManifestContract::class);

    $manifest->register(new DirectoryIconSet(
        'custom',                            // key: storage prefix, e.g. custom:default:xxx
        'Custom',                            // label: display name on the switcher bar
        base_path('resources/icons/custom'), // SVG directory (flat *.svg is fine)
        ['default'],                         // styles: style list; pass ['outline','solid'] for multi-style with subfolders
        'default',                           // defaultStyle: default style (optional, defaults to first of styles)
        true                                 // sanitize: whether to sanitize SVG (default true)
    ));
}

The directory convention is the same as Method 1 (flat = single style default, subfolder name = style), with optional meta.json overriding label / styles.

DirectoryIconSet constructor parameters

Parameter Type Required Default Description
$key string yes β€” Unique identifier; used as storage prefix and routing. Do not duplicate the built-in heroicons or an already-registered set (duplicate = override)
$label string yes β€” Display name on the switcher bar; can also be overridden by meta.json
$basePath string yes β€” Absolute / base path of the SVG directory; flat or subfolder-split styles both work
$styles ?array no null Style list; when null, auto-detected (subfolders β†’ subfolder names, otherwise ['default'])
$defaultStyle ?string no null Default style; when null, takes the first of $styles
$sanitize bool no true Whether to sanitize third-party SVGs via SvgSanitizer (built-in Heroicons are not sanitized, trusted)

7.4 Method 3: Implement the contract (dynamically generated / font classes / remote fetch)

When icons are not "a bunch of .svg files in a directory" (inlined-in-code, programmatically generated, remotely fetched, or font-class icons needing custom rendering), implement IconSetContract and register it in the host boot():

Method Returns Description
key(): string string Unique identifier (storage prefix, routing)
label(): string string Display name on the switcher bar
styles(): array array Style list; single-style returns ['default']
defaultStyle(): ?string ?string Default style; returns null when there is no style dimension
names(string $style): array array Bare icon-name list for a given style (without prefix)
svgMap(string $style): array array bare-name => inline-SVG, delivered all at once via the icon map
svg(string $fullOrName, ?string $style = null): ?string ?string Resolve and return the inline SVG; return null when not found. Accepts both the new set:style:name format and bare-name + style
use RaiseStudio\IconPicker\Icons\IconSetContract;

class BrandIconSet implements IconSetContract
{
    public function key(): string { return 'brand'; }
    public function label(): string { return 'Brand'; }
    public function styles(): array { return ['default']; }
    public function defaultStyle(): ?string { return 'default'; }

    public function names(string $style): array { return ['logo-a', 'logo-b']; }

    public function svgMap(string $style): array {
        return [
            'logo-a' => '<svg ...>...</svg>',
            'logo-b' => '<svg ...>...</svg>',
        ];
    }

    public function svg(string $fullOrName, ?string $style = null): ?string {
        if ($style === null && preg_match('/^brand:default:(.+)$/', $fullOrName, $m)) {
            $name = $m[1];
        } else {
            $name = $fullOrName;
        }
        return $this->icons[$name] ?? null;
    }
}

Register (also in host boot()): $manifest->register(new \App\Icons\BrandIconSet());

Registration note: IconManifest is bound as a singleton. The custom set you register() in host boot() resolves to the same instance that the field view data and the icon-map controller use, so nothing is lost.

7.5 Safe sanitization & cache refresh

  • Safe sanitization: Third-party SVGs are inlined via x-html, and by default pass through SvgSanitizer::clean(), stripping <script>, <foreignObject>, on* event attributes, and dangerous URIs like javascript: / data:text/html. Built-in Heroicons go through a dedicated trusted class and are not sanitized. For untrusted / complex SVGs, consider a more complete HTML sanitization solution.
  • Caching: Each set's names() / svgMap() are cached via Cache::rememberForever.
    • Add an entire new library directory (Method 1) β†’ auto-discovered, no cache clearing needed;
    • Append / modify SVGs in an existing directory (Method 1 / 2) β†’ php artisan cache:clear to refresh;
    • Method 3, changed code β†’ php artisan optimize:clear (or restart the service) to pick up the new class;
    • After changing package JS / CSS, you must re-run php artisan filament:assets and hard-refresh the browser.

7.6 Refreshing built-in Heroicons assets

The built-in Heroicons SVGs ship with the package. To refresh / regenerate the built-in assets:

php artisan filament-icon-picker:sync

8. Assets, Routes & Visuals

8.1 Route endpoints

The component registers two GET endpoints (both called by the front-end on demand):

Endpoint Purpose
/filament-icon-picker/icon-map?set=&style= Delivers a set's name => inline-SVG map for a given style in one request (single request, then purely client-side)
/filament-icon-picker/sets Returns the available icon-set list (switcher data source; currently also injected directly via field view data to avoid async failure)

8.2 Front-end architecture notes

  • One-shot SVG map: On modal open, fetch the icon map and store it in Alpine state; all subsequent rendering / search / style switching happens client-side with zero server round-trips.
  • Glassmorphic modal: backdrop-filter blur + semi-transparent border, 20px rounded corners.
  • Magnetic hover: transition: transform .3s cubic-bezier(.16,1,.3,1) + :hover { transform: scale(1.05) translateY(-2px) }.
  • Light / dark adaptation: Colors reference Filament panel CSS variables, automatically following light / dark / system themes with smooth transitions.
  • Scoped: Component styles are uniformly namespaced under the .fi-icon-picker-* prefix to avoid polluting the global scope.

9. FAQ

Q: Is it compatible with existing data? A: Fully compatible. The built-in set reads/writes the heroicon-o-xxx full name; custom sets use the set:style:name format. The two can coexist with zero migration.

Q: Can it support icon sets beyond the built-in Heroicons? A: Natively and at zero cost. A pure SVG library is auto-registered by dropping it into the host application's resources/icons/<library>/ (scans the app's resource_path('icons') by default, configurable via icon-picker.discover_path); a host-directory SVG registers in one line via DirectoryIconSet; dynamic / font / remote icons implement IconSetContract then register(). Storage is unified as set:style:name, and third-party SVGs are sanitized before inlining by default.

Q: Why a modal instead of a dropdown + icon preview? A: With 300+ icons, a dropdown is cramped and the search experience is poor; an independent modal grid is significantly better for preview and retrieval, with zero extra dependencies.

Q: The front-end didn't update after I changed a custom icon set? A: Appending / modifying SVGs in an existing directory needs php artisan cache:clear; changing JS / CSS needs php artisan filament:assets plus a browser hard-refresh.

Document status: multi-icon-set + modal + set switcher bar + style switching + recents / favorites + keyboard navigation + ARIA + directory auto-discovery + contract extension are all implemented. The three ways to integrate a custom icon set are covered in Chapter 7.