jaocero/radio-deck
FilamentPHP plugin that upgrades the default radio field into selectable “card” options with icons, titles, and descriptions. Built for Filament v4 (Laravel 11, PHP 8.2+, Tailwind v4) with support via custom theme @source directive.
composer require jaocero/radio-deck
php artisan make:filament-theme
resources/css/filament/admin/theme.css):
@source '../../../../vendor/jaocero/radio-deck/resources/views';
use JaOcero\RadioDeck\Forms\Components\RadioDeck;
RadioDeck::make('platform')
->options([
'ios' => 'iOS',
'android' => 'Android',
])
->icons([
'ios' => 'heroicon-m-device-phone-mobile',
'android' => 'heroicon-m-device-phone-mobile',
]);
Replace a standard Filament radio button group with a visually rich card-based selector for platform selection in a user profile form. Example:
public static function form(Form $form): Form
{
return $form->schema([
RadioDeck::make('preferred_platform')
->options([
'web' => 'Web',
'mobile' => 'Mobile',
'desktop' => 'Desktop',
])
->icons([
'web' => 'heroicon-o-globe-alt',
'mobile' => 'heroicon-o-phone',
'desktop' => 'heroicon-o-desktop-computer',
])
->descriptions([
'web' => 'Browser-based experience',
'mobile' => 'Optimized for phones',
'desktop' => 'Full desktop features',
])
->columns(2),
]);
}
Replace standard radio inputs with visual cards:
// Before
Radio::make('status')->options(['active', 'inactive']);
// After
RadioDeck::make('status')
->options([
'active' => 'Active',
'inactive' => 'Inactive',
])
->icons([
'active' => 'heroicon-o-check-circle',
'inactive' => 'heroicon-o-x-circle',
]);
Use enums for type safety and maintainability:
// Define enum
enum UserRole: string implements HasLabel, HasIcons
{
case ADMIN = 'admin';
case EDITOR = 'editor';
case VIEWER = 'viewer';
public function getLabel(): string
{
return match ($this) {
self::ADMIN => 'Administrator',
self::EDITOR => 'Editor',
self::VIEWER => 'Viewer',
};
}
public function getIcon(): string
{
return match ($this) {
self::ADMIN => 'heroicon-o-shield-check',
self::EDITOR => 'heroicon-o-pencil',
self::VIEWER => 'heroicon-o-eye',
};
}
}
// Usage
RadioDeck::make('role')
->options(UserRole::class)
->icons(UserRole::class);
Adjust column count per screen size:
RadioDeck::make('category')
->options(['books', 'movies', 'music'])
->columns([
'sm' => 1,
'md' => 2,
'lg' => 3,
]);
Dynamic colors based on state:
RadioDeck::make('priority')
->options(['low', 'medium', 'high'])
->colors([
'low' => 'gray',
'medium' => 'yellow',
'high' => 'red',
]);
Enable multiple selections with checkboxes:
RadioDeck::make('tags')
->options(['laravel', 'filament', 'livewire'])
->multiple()
->columns(3);
Theme Consistency:
->colors('primary') to match Filament's theme colorsextraCardsAttributes for border-radius, shadows, etc.Accessibility:
RadioDeck::make('accessibility')
->options(['high', 'medium', 'low'])
->descriptions([
'high' => 'Fully accessible',
'medium' => 'Partial accessibility',
'low' => 'Basic accessibility',
])
->iconSizes(IconSize::Large);
Form Validation:
RadioDeck::make('status')
->options(['published', 'draft'])
->required()
->rules(['required', 'in:published,draft']);
Dynamic Data:
RadioDeck::make('region')
->options(fn () => Region::query()->pluck('name', 'id'))
->descriptions(fn () => Region::query()->pluck('description', 'id'))
->icons(fn () => Region::query()->pluck('icon', 'id'));
Theme Configuration:
@source directive to custom theme CSSphp artisan filament:theme if theme isn't applying stylesMultiple Selection:
multiple()protected $casts = [
'selected_options' => 'array',
];
Icon Visibility:
heroicon-o-* (outline) variants instead of heroicon-m-* (mini) for better visibilityColumn Layout:
->columns(['sm' => 1, 'md' => 2, 'lg' => 3])
Enum Implementation:
HasIcons or HasDescriptionsuse JaOcero\RadioDeck\Contracts\HasIcons;
use JaOcero\RadioDeck\Contracts\HasDescriptions;
View Compilation:
php artisan view:clearphp artisan filament:themeState Persistence:
protected $casts = [
'platform' => 'string', // For single selection
'tags' => 'array', // For multiple selection
];
CSS Conflicts:
!important in extraCardsAttributes as last resort:
->extraCardsAttributes(['class' => '!rounded-xl'])
Custom Components:
// Create custom card component
RadioDeck::make('custom')
->extraCardsAttributes([
'class' => 'bg-gradient-to-r from-blue-500 to-purple-600',
'x-data' => '{}',
]);
Dynamic Content:
RadioDeck::make('dynamic_options')
->options(fn () => $this->getDynamicOptions())
->descriptions(fn () => $this->getDescriptions())
->reactive();
Event Handling:
RadioDeck::make('event_trigger')
->options(['send', 'archive'])
->extraOptionsAttributes([
'x-on:click' => 'handleSelection($event, $value)'
]);
Localization:
RadioDeck::make('language')
->options([
'en' => __('English'),
'es' => __('Spanish'),
])
->descriptions([
'en' => __('English description'),
'es' => __('Spanish description'),
]);
Lazy Loading:
// For large option sets
RadioDeck::make('large_set')
->options(fn () => Option::query()->limit(50)->get())
->descriptions(fn () => Option::query()->limit(50)->pluck('description'))
->icons(fn () => Option::query()->limit(50)->pluck('icon'));
Memoization:
// Cache expensive option generation
RadioDeck::make('cached_options')
->options(fn () => cache()->remember('radio_deck_options', now()->addHours(1), function () {
return Option::all();
}));
Asset Optimization:
How can I help you explore Laravel packages today?