schmeits/filament-4-phosphor-icons
Bring the Phosphor icon library to Filament 4 panels. Use thousands of crisp, consistent icons across forms, tables, actions, and navigation, with straightforward setup for Laravel admin interfaces.
composer require schmeits/filament-4-phosphor-icons
It ships no assets, no migrations, no publish step — just an enum plus a service provider that maps each case to a Phosphor SVG. Since 1.1.0 the floor is Filament 4.13.2 or 5.8.2 (the first versions without known security advisories) and Laravel 12 or 13; Laravel 11 is EOL and no longer tested. If you're on Filament 3 there is still no backport path.
Look at vendor/schmeits/filament-4-phosphor-icons/src/ first. Two greps tell you everything:
grep -n "enum \|case " vendor/schmeits/filament-4-phosphor-icons/src/*.php | head -40
grep -n "public function " vendor/schmeits/filament-4-phosphor-icons/src/*.php | head -20
The first shows the case names (and their backing values), the second shows the six weight variants — expect fluent methods like ->thin(), ->light(), ->regular(), ->bold(), ->fill(), ->duotone(). The enum is generated, so its header comment records which codeat3/blade-phosphor-icons version it came from — read that before assuming an icon is missing rather than just not generated yet. Confirm the FQCN before you write it into 40 files.
First use case — swap one heroicon for a Phosphor one:
use Schmeits\FilamentPhosphorIcons\PhosphorIcon;
class CustomerResource extends Resource
{
protected static string | BackedEnum | null $navigationIcon = PhosphorIcon::UsersThree;
}
Filament 4/5 icon slots are typed string | Htmlable | BackedEnum | null, which is exactly why the enum API exists: it drops in wherever you previously wrote 'heroicon-o-users'. Reload, and it renders at the panel's icon size because the SVG inherits currentColor and the fi-icon sizing classes. If nothing renders, you have a version constraint problem, not a code problem — check composer why-not schmeits/filament-4-phosphor-icons. On a Filament 4.0–4.13.1 install the correct fix is to upgrade Filament, not to pin an older icon package.
Panel and resource chrome. Replace $navigationIcon, $activeNavigationIcon, $modelIcon (used in global search and relation managers), and getNavigationBadge(). Because nav icons are static properties, this is a mechanical find-and-replace across resources — do it once, on a branch, and eyeball every screen.
Actions and bulk actions.
Action::make('export')
->icon(PhosphorIcon::Export)
->label('Export'),
BulkAction::make('archive')->icon(PhosphorIcon::ArchiveBox),
Icon-only buttons still need a ->tooltip() or ->label() — the SVG carries no accessible title.
Tables and infolists. Use closures for state-driven icons:
IconColumn::make('status')
->icon(fn (Order $record) => $record->isPaid()
? PhosphorIcon::CheckCircle
: PhosphorIcon::Clock),
TextColumn::make('email')->icon(PhosphorIcon::EnvelopeSimple),
Forms. TextInput::make('email')->prefixIcon(PhosphorIcon::Envelope), Section::make('Billing')->icon(PhosphorIcon::CreditCard). If a particular affix slot in your Filament minor version is typed string only, pass PhosphorIcon::Envelope->value — same rendered output.
Standardise on a weight, once. The real payoff of six weights is a consistent visual language: regular for nav, fill for the active/selected state, bold for 16px-and-under contexts (badges, table cells), thin only in dense data tables. Encode it in one helper so the decision lives in one file:
final class Icon
{
public static function nav(PhosphorIcon $icon, bool $active = false): PhosphorIcon|string
{
return $active ? $icon->fill() : $icon->regular();
}
}
Override Filament's own icons globally. Instead of chasing every panels:: alias, register the mappings once in AppServiceProvider::boot():
FilamentIcon::register([
'panels::widgets.account' => PhosphorIcon::UserCircle,
'actions::delete-action' => PhosphorIcon::Trash,
'tables::columns.icon-column.true' => PhosphorIcon::CheckCircle,
]);
That gives you a whole-panel consistent icon set in one place. Guard with class_exists(PhosphorIcon::class) if the package is optional in some deployments.
User-selectable icons. Store the backed value string and let Laravel's native enum cast handle the round trip:
// Model
protected $casts = ['icon' => PhosphorIcon::class];
// Form
Select::make('icon')
->options(collect(PhosphorIcon::cases())
->mapWithKeys(fn (PhosphorIcon $i) => [$i->value => $i->name])
->all())
->searchable();
Blade. <x-filament::icon :icon="$icon" class="h-5 w-5" />. Sizing only comes for free inside Filament components that accept ->size(IconSize::Small); in raw Blade you supply the h-* w-* classes yourself.
Regenerate instead of forking. When codeat3/blade-phosphor-icons adds a batch of icons, bump that dependency and run composer generate-enum to rebuild the enum from upstream rather than hand-editing cases. The generator lives at bin/generate-enum.php and stamps the upstream version into the output, so the diff tells you exactly what moved.
Isolate the dependency. If you're putting this into a client project, define App\Enums\AppIcon that wraps the vendor enum (or even just string values). Swapping icon sets later becomes a one-file change instead of a grep across app/Filament.
extraAttributes-style APIs want a string. ->value is always a safe fallback.->value is what lands in HTML and in the database. Never rename or reorder cases if you've persisted them; and persist ->value, not ->name, unless your hydration path maps names back with constant().from() throws. Stale DB rows or renamed cases produce a ValueError mid-render. Use PhosphorIcon::tryFrom($v) ?? PhosphorIcon::Question on any user-editable or legacy column.codeat3/blade-phosphor-icons ≥2.4. Check the version header in the generated enum, bump the blade package, and re-run composer generate-enum before you start hand-rolling SVGs.fill and duotone weights rely on fill/opacity and can look heavy or washed out against Filament's dark-mode gray-950 surfaces. Check both themes before committing a convention.FilamentIcon::register(). php artisan filament:clear-cached-components && php artisan optimize:clear — cached components and compiled views can hold the old alias and you'll swear the mapping didn't take.require-dev it. SVGs are served from vendor/; a composer install --no-dev in production (or a Docker build context that prunes vendor/schmeits) is the usual cause of missing icons.{{ $icon }} on a backed enum is context-dependent and will bite you in plain Blade templates — write {{ $icon->value }} explicitly.declare(strict_types=1). Irrelevant if you only consume the enum; relevant if you've copy-pasted or subclassed the service provider. The 1.1.0 cleanup removed a $viewNamespace property, a facade alias that was never real, and unused frontend tooling — no live public API changed, so nothing to chase in your app."^1.1", commit the lock file, and if it ever stalls, fork it or move the cases into your own enum and register the paths with FilamentAsset/FilamentIcon. Don't let it leak into domain code.composer generate-enum, or point FilamentIcon::register() at your own SVG paths for anything the package doesn't cover, keeping everything else on the enum so the weights stay consistent.aria-label, or a ->label(); Filament renders the SVG as decorative and screen readers get nothing.How can I help you explore Laravel packages today?