artisanpack-ui/icons
Register and use your own SVG icon sets in Laravel with minimal overhead. Integrates with blade-ui-kit/blade-icons and Livewire UI, supports config or event-based registration, and makes it easy to add premium sets like Font Awesome Pro.
This document explains the architectural design of ArtisanPack UI Icons v2.0, including the adapter pattern implementation, integration with blade-ui-kit/blade-icons, and the hybrid registration system.
ArtisanPack UI Icons v2.0 is built on the adapter pattern, transforming from a hardcoded icon provider into an extensibility layer that integrates custom icon sets with Laravel's Blade component system.
┌─────────────────────────────────────────────────────────────┐
│ ArtisanPack UI Icons v2.0 │
│ (Extensibility Layer) │
├─────────────────────────────────────────────────────────────┤
│ Config-Based Registration │ Event-Driven Registration │
│ ┌─────────────────────────┐ │ ┌─────────────────────────┐ │
│ │ config/artisanpack/icons.php │ │ │ Third-Party Packages │ │
│ │ │ │ │ via Filter Hooks │ │
│ └─────────────────────────┘ │ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ IconsServiceProvider │
│ (Hybrid Registration) │
├─────────────────────────────────────────────────────────────┤
│ blade-ui-kit/blade-icons │
│ (SVG Component Rendering) │
├─────────────────────────────────────────────────────────────┤
│ Laravel Blade Engine │
│ (Component Resolution) │
└─────────────────────────────────────────────────────────────┘
The v2.0 architecture implements the adapter pattern to bridge the gap between custom icon sets and the standardized blade-ui-kit/blade-icons system.
// v1.x - Hardcoded approach
class Icons {
protected $icons = [
'home' => '<svg>...</svg>',
'user' => '<svg>...</svg>',
// 17,000+ hardcoded icons
];
}
// v2.0 - Adapter approach
class IconsServiceProvider {
public function boot() {
// Adapt custom icon sets to BladeIcons format
$this->registerIcons();
}
protected function registerIcons() {
$iconSets = $this->collectIconSets();
foreach ($iconSets as $set) {
BladeIcons::add($set['prefix'], [
'path' => $set['path'],
'prefix' => $set['prefix'],
]);
}
}
}
The package integrates deeply with blade-ui-kit/blade-icons to provide seamless SVG component rendering.
use BladeUI\Icons\Factory as BladeIcons;
// Register icon set with BladeIcons factory
BladeIcons::add('prefix', [
'path' => '/path/to/svg/files',
'prefix' => 'prefix',
]);
// This enables components like:
// <x-icon-prefix-home />
// <x-icon-prefix-user />
1. Blade Template: <x-icon-fa-home class="w-6 h-6" />
↓
2. Laravel Component Resolution
↓
3. BladeIcons Factory Lookup (prefix: 'fa', name: 'home')
↓
4. SVG File Loading (/path/to/fa/home.svg)
↓
5. SVG Content Rendering with Attributes
↓
6. Final HTML: <svg class="w-6 h-6">...</svg>
Icon registration is deferred until the BladeCompiler is resolved to ensure proper initialization order:
$this->app->callAfterResolving(BladeCompiler::class, function () {
$this->registerIcons();
});
The v2.0 architecture implements a hybrid registration system that combines two registration methods.
config/artisanpack/icons.php// config/artisanpack/icons.php
return [
'sets' => [
['path' => resource_path('icons/fa'), 'prefix' => 'fa'],
['path' => resource_path('icons/custom'), 'prefix' => 'custom'],
],
];
// In a package service provider
addFilter('ap.icons.register-icon-sets', function ($sets) {
$sets[] = new IconSetRegistration(
path: __DIR__ . '/../../resources/icons',
prefix: 'mypackage'
);
return $sets;
});
protected function collectIconSets(): array
{
// 1. Collect event-driven registrations
$eventSets = apply_filters('ap.icons.register-icon-sets', []);
// 2. Get config-based registrations
$configSets = config('artisanpack.icons.sets', []);
// 3. Merge with config precedence
$allSets = array_merge($eventSets, $configSets);
// 4. Validate and filter
return array_filter($allSets, [$this, 'validateIconSet']);
}
protected function validateIconSet(array $set): bool
{
return isset($set['path']) &&
isset($set['prefix']) &&
is_dir($set['path']);
}
<?php
// config/artisanpack/icons.php
return [
/*
|--------------------------------------------------------------------------
| Custom Icon Sets
|--------------------------------------------------------------------------
|
| Register your custom SVG icon sets here. Each set requires:
| - path: Absolute path to directory containing SVG files
| - prefix: Unique prefix for Blade components (e.g., 'fa', 'hero')
|
*/
'sets' => [
// Font Awesome Pro icons
[
'path' => resource_path('icons/fontawesome/solid'),
'prefix' => 'fas',
],
[
'path' => resource_path('icons/fontawesome/regular'),
'prefix' => 'far',
],
[
'path' => resource_path('icons/fontawesome/brands'),
'prefix' => 'fab',
],
// Heroicons
[
'path' => resource_path('icons/heroicons/outline'),
'prefix' => 'hero-outline',
],
[
'path' => resource_path('icons/heroicons/solid'),
'prefix' => 'hero-solid',
],
// Custom application icons
[
'path' => resource_path('icons/custom'),
'prefix' => 'custom',
],
],
];
Each icon set must follow these rules:
.svg file// ❌ Missing required keys
['path' => '/path/to/icons'], // Missing prefix
// ❌ Non-existent directory
['path' => '/non/existent/path', 'prefix' => 'test'],
// ❌ Invalid prefix characters
['path' => resource_path('icons'), 'prefix' => 'my_icons!'],
// ❌ Conflicting prefixes (will cause overwrites)
['path' => resource_path('icons/set1'), 'prefix' => 'fa'],
['path' => resource_path('icons/set2'), 'prefix' => 'fa'], // Conflict!
| Aspect | v1.x | v2.0 |
|---|---|---|
| Startup Memory | ~50MB (17K+ icons) | ~1KB (metadata only) |
| Per Icon | Preloaded in memory | Loaded on-demand |
| Total Reduction | - | 95%+ improvement |
v1.x Loading Process:
Application Start → Load 17K+ Icons → Memory Allocation → Ready
~500ms ~2GB RAM
v2.0 Loading Process:
Application Start → Register Metadata → Ready → Load Icon on Use
~10ms ~1KB RAM ~5ms per icon
The v2.0 architecture leverages multiple caching layers:
config:cache caches merged configurationprotected function validateIconSet(array $set): bool
{
// Required keys validation
if (!isset($set['path']) || !isset($set['prefix'])) {
$this->logWarning('Icon set missing required keys', $set);
return false;
}
// Path existence validation
if (!is_dir($set['path'])) {
$this->logWarning("Icon directory not found: {$set['path']}", $set);
return false;
}
// Path readability validation
if (!is_readable($set['path'])) {
$this->logWarning("Icon directory not readable: {$set['path']}", $set);
return false;
}
// Prefix format validation
if (!preg_match('/^[a-zA-Z0-9-]+$/', $set['prefix'])) {
$this->logWarning("Invalid prefix format: {$set['prefix']}", $set);
return false;
}
return true;
}
protected function registerIconSet(array $set): void
{
try {
BladeIcons::add($set['prefix'], $set);
} catch (\Exception $e) {
$this->logError("Failed to register icon set '{$set['prefix']}'", [
'error' => $e->getMessage(),
'set' => $set,
]);
}
}
Symptoms:
Solutions:
// Check configuration
php artisan tinker
> config('artisanpack.icons.sets')
// Verify directory exists
> is_dir(resource_path('icons/fontawesome'))
// Check for SVG files
> glob(resource_path('icons/fontawesome') . '/*.svg')
// Clear caches
php artisan config:clear
php artisan view:clear
Symptoms:
Solutions:
// Check for duplicate prefixes in config
$sets = config('artisanpack.icons.sets');
$prefixes = array_column($sets, 'prefix');
$duplicates = array_diff_assoc($prefixes, array_unique($prefixes));
// Use unique prefixes
'prefix' => 'mypackage-admin' // Instead of generic 'admin'
Symptoms:
Solutions:
// Check icon set sizes
foreach (config('artisanpack.icons.sets') as $set) {
$count = count(glob($set['path'] . '/*.svg'));
echo "Set '{$set['prefix']}': {$count} icons\n";
}
// Consider splitting large sets
// Instead of one huge set:
['path' => resource_path('icons/all'), 'prefix' => 'all'],
// Use multiple smaller sets:
['path' => resource_path('icons/navigation'), 'prefix' => 'nav'],
['path' => resource_path('icons/actions'), 'prefix' => 'action'],
Symptoms:
Solutions:
// Debug event-driven registration
addFilter('ap.icons.register-icon-sets', function (IconSetRegistration $registry) {
$beforeCount = count($registry->getSets());
\Log::debug('MyPackage registering icons', [
'path' => __DIR__ . '/../../resources/icons',
'prefix' => 'mypackage',
'existing_sets_count' => $beforeCount
]);
$registry->addSet(__DIR__ . '/../../resources/icons', 'mypackage');
\Log::debug('Icon sets after MyPackage', ['count' => count($registry->getSets())]);
return $registry;
});
A: Yes, the hybrid system is designed for this. Each icon set uses a unique prefix, allowing you to mix Font Awesome, Heroicons, custom icons, etc.
A: Use descriptive prefixes. Instead of fa-home and hero-home, consider fa-solid-home and hero-outline-home.
A: No, config-based registrations always take precedence over event-driven ones.
A: blade-ui-kit handles SVG parsing errors gracefully, typically rendering nothing or showing an error in development mode.
A: Use php artisan config:cache, ensure SVG files are optimized, and consider using a CDN for icon assets.
A: Currently, the system requires local file paths. Consider syncing cloud icons to local storage or extending the IconSetRegistration class to support remote paths.
How can I help you explore Laravel packages today?