Skip to main content
IconPickerField ← Back to Table of Contents

Summary

Searchable blade-icons picker with lazy SVG rendering, set filters, whitelist/exclude controls, paginated server-side search, and virtual scrolling for large catalogs. Stores the full icon name string (for example heroicon-o-star, gravityui-star).
ClassBjanczak\FilamentFlexFields\Filament\Forms\Components\IconPickerField
ExtendsFilament\Forms\Components\Field
State typestring|null
FieldTypeicon_picker

Basic usage

use Bjanczak\FilamentFlexFields\Filament\Forms\Components\IconPickerField;

IconPickerField::make('menu_icon')
    ->label('Menu icon')
    ->sets(['heroicons', 'gravity-icons'])
    ->iconsOnly()
    ->gridColumns(8)
    ->preload()
    ->required();

IconPickerField::make('status_icon')
    ->label('Status icon')
    ->sets(['heroicons'])
    ->icons([
        'heroicon-o-check-circle',
        'heroicon-o-exclamation-triangle',
        'heroicon-o-x-circle',
    ])
    ->gridColumns(4)
    ->size('sm')
    ->variant('soft');
Limit the field to a single installed library:
IconPickerField::make('gravity_icon')
    ->label('Gravity icons only')
    ->sets(['gravity-icons'])
    ->helperText('Limits the catalog to the gravity-icons set.');

State format

ValueDescriptionExample
Selected iconFull blade-icons name stored as a plain stringheroicon-o-star
Emptynull when cleared or never selectednull
The stored value is the same string you would pass to Filament generate_icon_html() or <x-icon> — no extra wrapper object. Use the value anywhere Filament accepts an icon name:
->icon(fn (Get $get): ?string => $get('menu_icon'))

Usage on the frontend

The field stores the full blade-icons name in your database (for example heroicon-o-star, gravityui-star). On the frontend, treat it like any other static icon name — no JSON decoding or custom serializer. Assuming the icon is saved on a $category model as $category->icon:
@if (filled($category->icon))
    <x-icon :name="$category->icon" class="h-6 w-6" />
@endif
In PHP / Filament views:
use function Filament\Support\generate_icon_html;

echo generate_icon_html($category->icon)?->toHtml();
In Livewire or Inertia props, pass the string through unchanged. Your public site only needs the same blade-icons sets installed (Heroicons, Gravity UI, etc.) — the stored value is already the correct <x-icon> name. For read-only display in Filament admin tables, use IconColumn instead of rebuilding the cell yourself.

Validation

BehaviourDetail
Built-inCustom rule checks the value against the configured catalog (sets, whitelist, exclude list)
required()Empty / null state fails with the standard required message
Invalid iconFails with filament-flex-fields::default.validation.icon_picker.invalid
Whitelist mode (->icons([...])) is strict: only listed names pass validation, even if they exist in a broader set.

Setup

IconPickerField reads any blade-icons set registered in your Laravel app (Heroicons ship with Filament; Gravity UI icons ship with this plugin via janczakb/blade-gravity-icons). Set names vs prefixessets() accepts either the blade-icons set key or an icon prefix:
You passResolves to
heroiconsHeroicons set
gravity-iconsGravity UI set
gravityuiSame Gravity UI set (matched by prefix)
heroiconHeroicons set (matched by prefix)
When ->sets() is omitted, all installed sets are indexed. Restrict globally via config:
// config/filament-flex-fields.php
'icon_picker_sets' => ['heroicons', 'gravity-icons'],
Bundled manifest (recommended for production) — speeds up cold catalog reads:
php artisan fff:icons:manifest
php artisan fff:icons:manifest --sets=heroicons --sets=gravity-icons
Writes resources/dist/icon-catalog-manifest.json. Disable with icon_picker_use_bundled_manifest => false if you prefer live blade-icons scanning.

Configuration API

sets(array|string|Closure|null $sets = null)

Limit available icon libraries. null uses all installed sets, or config('filament-flex-fields.ui.icon_picker_sets') when that config is a non-empty array.
IconPickerField::make('icon')->sets(['heroicons']);
IconPickerField::make('icon')->sets('gravityui');

icons(array|Closure $icons)

Whitelist only these full icon names. Set tabs and search operate inside the whitelist.
IconPickerField::make('icon')->icons([
    'heroicon-o-star',
    'gravityui-star',
]);

excludeIcons(array|Closure $icons)

Remove icons from the catalog after sets are resolved.
IconPickerField::make('icon')->excludeIcons(['heroicon-o-x-mark']);

searchResultsLayout(string|Closure $layout)

Dropdown result layout. Default: icons.
LayoutDescription
iconsIcon-only grid cells (default)
gridGrid with icon + human-readable label
listVertical list rows with icon + label
Shorthand helpers:
IconPickerField::make('icon')->iconsOnly(); // icons
IconPickerField::make('icon')->grid();    // grid
IconPickerField::make('icon')->list();    // list

gridColumns(int|Closure $columns)

Column count for grid / icons layouts. Clamped to 2–12. Default: 8.
IconPickerField::make('icon')->gridColumns(6);

closeOnSelect(bool|Closure $condition = true)

Close the teleported panel after picking an icon. Default: true.
IconPickerField::make('icon')->closeOnSelect(false);

preload(bool|Closure $condition = true)

Fetch the first results page when the Alpine component mounts (before the panel opens). Default: false.
IconPickerField::make('icon')->preload();

limitPerSet(int|Closure|null $limit)

Cap how many icons are indexed per set. Useful for demos or constrained pickers.
IconPickerField::make('icon')->limitPerSet(100);

perPage(int|Closure $perPage)

Server page size for search / infinite scroll. Default: 64, maximum: 96.
IconPickerField::make('icon')->perPage(32);

clearable(bool|Closure $condition = true)

Show the clear (×) control when a value is selected. Default: true.
IconPickerField::make('icon')->clearable(false);

variant(string|Closure $variant)

Visual shell shared with SelectField. Values: bordered (default), secondary, flat, soft, faded, underlined. Legacy primary maps to bordered.
IconPickerField::make('icon')->variant('soft');

size(string|ControlSize|Closure $size)

Control height. See Control size. Default: md, or config('filament-flex-fields.ui.icon_picker_size').
IconPickerField::make('icon')->size('lg');

placeholder(string|Closure|null $placeholder)

Trigger placeholder when no icon is selected. Default translation: Select an icon.
IconPickerField::make('icon')->placeholder('Pick an icon…');

readOnly(bool|Closure $condition = true)

Inherited from Filament CanBeReadOnly. Prevents opening the panel and clearing the value.
IconPickerField::make('icon')->readOnly();

focusOutline(bool|Closure $condition = true)

Inherited from HasFieldFocusOutline.
IconPickerField::make('icon')->focusOutline();

chevronIcon() / clearIcon() / selectedOptionCheckIcon()

Inherited from HasSelectFieldIcons. Override trigger chevron, clear button, or selected checkmark. Defaults come from config('filament-flex-fields.ui.select_*_icon') or Gravity UI fallbacks.
IconPickerField::make('icon')
    ->chevronIcon('heroicon-o-chevron-down')
    ->clearIcon('heroicon-o-x-mark');

Prefix / suffix affixes

Inherited from Filament HasAffixesprefixIcon(), suffixIcon(), prefixActions(), suffixActions(), and related helpers work on the trigger shell.
IconPickerField::make('icon')
    ->prefixIcon('heroicon-o-sparkles')
    ->suffixIcon('heroicon-o-information-circle');

Public helper methods

MethodReturnsDescription
getConfiguredSets()list<string>|nullRaw sets() config
getResolvedSetNames()list<string>Normalized blade-icons set keys
getWhitelistedIcons()list<string>Whitelist from icons()
getExcludedIcons()list<string>Exclusions from excludeIcons()
getSearchResultsLayout()stringgrid, list, or icons
getGridColumns()intClamped column count
shouldPreload()boolPreload flag
shouldCloseOnSelect()boolClose-on-select flag
getPerPage()intPage size
getLimitPerSet()int|nullPer-set cap
isClearable()boolClear button enabled
getVariant()stringResolved variant
getSize()stringResolved size
isAllowedIcon(string $icon)boolWhether the icon is in the effective catalog
searchIcons(string $query, ?string $set, int $page)arrayRanked search payload
renderIconHtml(?string $icon)stringSVG HTML for trigger / previews
renderIconSvgs(array $icons)list<array{name, html}>Batch SVG render with server cache
getIconPickerSearchResults(...)arrayLivewire @Renderless search endpoint
getIconPickerSvgPreviews(array $icons)list<array{name, html}>Livewire @Renderless SVG batch (max 48 icons)
getWrapperClasses()array<string, bool|string>CSS class map for the trigger
getAvailableSetsForJs()list<array>Set summaries for Alpine (key, prefix, label, count)

FlexField schema config

When using FieldType::IconPicker / FlexFieldFormBuilder:
Config keyTypeMaps to
setsarray|string|nullsets()
iconsstring[]icons()
exclude_iconsstring[]excludeIcons()
search_results_layout / layoutgrid|list|iconssearchResultsLayout()
close_on_selectboolcloseOnSelect()
grid_columnsintgridColumns()
preloadboolpreload()
limit_per_setint|nulllimitPerSet()
per_pageintperPage()
sizesm|md|lgsize() — default from icon_picker_size
variantstringvariant() — default from icon_picker_variant
new FlexFieldDefinition(
    slug: 'menu_icon',
    label: 'Menu icon',
    type: FieldType::IconPicker,
    config: [
        'sets' => ['heroicons'],
        'layout' => 'icons',
        'grid_columns' => 8,
        'preload' => true,
    ],
);

Global config

KeyDefaultPurpose
icon_picker_setsnullDefault sets() when not set on the field
icon_picker_sizemdDefault size for form-builder fields
icon_picker_variantborderedDefault variant for form-builder fields
icon_picker_index_cache_days7TTL for indexed catalog pools
icon_picker_catalog_cache_days7TTL for resolved set catalogs
icon_picker_svg_cache_days30TTL for rendered SVG HTML
icon_picker_search_cache_minutes60TTL for ranked search responses (0 disables)
icon_picker_use_bundled_manifesttruePrefer icon-catalog-manifest.json over live scanning
Search result caching is skipped when a whitelist or exclude list is configured, so filtered catalogs always stay accurate.

UX

  • Trigger — Select-style pill with live SVG preview of the chosen icon, clear button, and chevron. Shares SelectField + teleported-menu styling.
  • Set filter — Tab-style filter when multiple libraries are available, with per-set counts. Hidden when only one set is configured.
  • Search — Debounced server search with term highlighting in labels and a clear-search control.
  • Keyboard — Arrow keys move the active cell, Enter selects, Escape closes the panel and restores focus (preventScroll on focus return).
  • Infinite scroll — Intersection Observer sentinel at the list bottom loads the next page; the following page is prefetched in the background.
  • Loading — Skeleton cells while the first page or SVG previews load.
  • Accessibility — Combobox-style trigger with aria-expanded, aria-controls, and an associated results list.

Performance notes

  • Indexed catalogIconCatalogIndex precomputes labels and O(1) allowed-icon lookup.
  • Ranked search — Exact name matches rank above partial / label matches.
  • Lean Livewire payloads — Set summaries ship only on the first unfiltered page; pagination responses carry icon names only.
  • SVG cache — Rendered SVG HTML is cached server-side and batched (max 48 icons per Livewire call).
  • Viewport SVG loading — The browser fetches SVG previews only for icons entering the scroll viewport.
  • Virtual window — Long lists mount only visible rows plus a small buffer (~48 cells) while preserving scroll height.
  • Client search cache — Repeated queries are served from an in-memory LRU Map (max 32 entries) without extra round-trips.
  • Lazy assetsicon-picker-field, select-field, and teleported-menu CSS/JS load through the flex-field asset injector.

CSS classes

ClassRole
fff-icon-pickerAlpine root inside the trigger
fff-icon-picker-fieldField wrapper modifier on fff-select-field
fff-icon-picker-field--layout-{grid|list|icons}Layout modifier on wrapper
fff-icon-picker__previewSelected icon SVG in trigger
fff-icon-picker__panelTeleported dropdown (x-teleport="body")
fff-icon-picker__toolbarSearch + set tabs
fff-icon-picker__set-tabs / fff-icon-picker__set-tabLibrary filter chips
fff-icon-picker__resultsScrollable results container
fff-icon-picker__grid / fff-icon-picker__optionGrid cells
fff-icon-picker__track / fff-icon-picker__track--virtualVirtual scroll track
fff-icon-picker__skeletonLoading placeholder cell
Trigger shell classes come from fff-select-field (fff-select-field--{size}, fff-select-field--{variant}, fff-select-field--clearable-has-value).

Model & persistence

// Migration
$table->string('menu_icon')->nullable();

// Model — no special cast required
protected $fillable = ['menu_icon'];
Editing an existing record:
IconPickerField::make('menu_icon')
    ->label('Menu icon')
    ->sets(['heroicons'])
    ->default(fn (?MenuItem $record): ?string => $record?->menu_icon);
Use the stored icon name in Blade or Filament actions:
// Table column
TextColumn::make('menu_icon')
    ->icon(fn (?string $state): ?string => $state);

// Infolist
TextEntry::make('menu_icon')
    ->icon(fn (?string $state): ?string => $state);

Recipes

CMS menu icon — multi-set catalog

IconPickerField::make('menu_icon')
    ->label('Navigation icon')
    ->sets(['heroicons', 'gravity-icons'])
    ->iconsOnly()
    ->gridColumns(8)
    ->preload()
    ->closeOnSelect(true)
    ->required();

Strict whitelist — status icons only

IconPickerField::make('status_icon')
    ->label('Status')
    ->sets(['heroicons'])
    ->icons([
        'heroicon-o-check-circle',
        'heroicon-o-exclamation-triangle',
        'heroicon-o-x-circle',
        'heroicon-o-information-circle',
    ])
    ->gridColumns(4)
    ->size('sm')
    ->variant('soft')
    ->clearable(false);

Read-only display on view / audit page

IconPickerField::make('chosen_icon')
    ->label('Selected icon')
    ->default(fn ($record) => $record->chosen_icon)
    ->readOnly()
    ->sets(['heroicons']);

Large catalog — preload + virtual scroll tuning

IconPickerField::make('feature_icon')
    ->label('Feature icon')
    ->sets(['heroicons', 'gravity-icons'])
    ->preload()
    ->perPage(48)
    ->grid()
    ->gridColumns(6)
    ->limitPerSet(200)
    ->helperText('Preloads the first page; scroll loads more icons on demand.');

Grid vs list layout comparison

// Compact icon grid (default icons-only cells)
IconPickerField::make('icon_grid')
    ->iconsOnly()
    ->gridColumns(10);

// Labelled grid rows
IconPickerField::make('icon_grid_labels')
    ->grid()
    ->gridColumns(6);

// Vertical list with icon + searchable label
IconPickerField::make('icon_list')
    ->list()
    ->perPage(32);

Flex-field schema recipe

use Bjanczak\FilamentFlexFields\Enums\FieldType;
use Bjanczak\FilamentFlexFields\Support\FlexFieldDefinition;

new FlexFieldDefinition(
    slug: 'nav_icon',
    label: 'Navigation icon',
    type: FieldType::IconPicker,
    config: [
        'sets' => ['heroicons'],
        'layout' => 'icons',
        'grid_columns' => 8,
        'preload' => true,
        'size' => 'md',
        'variant' => 'bordered',
    ],
);

See also

  • IconColumn — read-only table display for saved icon values

Playground

Enable the playground (FLEX_FIELDS_PLAYGROUND=true) and open Flex Fields Playground → Icon picker (icon-picker-field) to compare:
  • Heroicons-only vs Gravity-icons-only pickers
  • Grid, whitelist, size, variant, and clearable demos

Implementation notes

  • Dropdown panel uses x-teleport="body" and createSearchableSelectMenuMixin() for positioning, same as SelectField / CountryField.
  • Requires installed blade-icons sets; empty catalogs mean no icons are listed.
  • getIconPickerSvgPreviews() silently drops unknown or disallowed icon names.
  • Reopen flow resets virtual scroll and re-syncs viewport SVGs without calling Alpine $watch cleanup (Alpine magic $watch does not return an unwatcher).