jaocero / radio-deck
Turn Filament's radio and checkbox list into selectable cards with icons, titles, descriptions and colors.
Fund package maintenance!
Requires
- php: ^8.2
- filament/forms: ^4.0|^5.0
- illuminate/contracts: ^11.28|^12.0|^13.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- filament/filament: ^4.0|^5.0
- laravel/pint: ^1.24
- nunomaduro/collision: ^8.1
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-arch: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-27 14:12:06 UTC
README
Turn Filament's radio and checkbox list into selectable cards with icons, titles, descriptions and colors.
Requirements
- PHP 8.2+
- Laravel 11.28+
- Filament v4.x or v5.x
- A custom Filament theme (Tailwind CSS v4)
Installation
composer require jaocero/radio-deck
Add the plugin's views to your custom theme's CSS file (usually resources/css/filament/admin/theme.css), then rebuild your assets:
@source '../../../../vendor/jaocero/radio-deck/resources/views';
Publish the plugin's JavaScript (used by searchable() and bulkToggleable()):
php artisan filament:assets
Usage
use Filament\Schemas\Schema; use Filament\Support\Enums\Alignment; use Filament\Support\Enums\IconPosition; use Filament\Support\Enums\IconSize; use Filament\Support\Icons\Heroicon; use JaOcero\RadioDeck\Forms\Components\RadioDeck; public static function configure(Schema $schema): Schema { return $schema ->components([ RadioDeck::make('platform') ->options([ 'ios' => 'iOS', 'android' => 'Android', 'web' => 'Web', ]) ->descriptions([ 'ios' => 'iOS mobile app', 'android' => 'Android mobile app', 'web' => 'Web app', ]) ->icons([ 'ios' => Heroicon::OutlinedDevicePhoneMobile, 'android' => 'heroicon-o-device-phone-mobile', 'web' => Heroicon::OutlinedGlobeAlt, ]) ->required() ->iconSize(IconSize::Medium) // ExtraSmall | Small | Medium | Large | ExtraLarge | TwoExtraLarge, or classes like 'size-12' ->iconPosition(IconPosition::Before) // Before | After ->alignment(Alignment::Center) // Start | Center | End ->optionsGap('gap-5') // Gap between the icon and the text inside a card ->padding('px-4 py-6') // Padding inside each card ->extraCardsAttributes(['class' => 'rounded-xl']) ->extraOptionsAttributes(['class' => 'text-lg']) ->extraDescriptionsAttributes(['class' => 'text-sm font-light']) ->color('primary') // Color of every option ->columns(3), // or ['sm' => 1, 'md' => 2, 'lg' => 3] ]); }
Colors
Use one color for every option, or a color per option. Colors can be names registered with Filament or palettes from Filament\Support\Colors\Color:
use Filament\Support\Colors\Color; RadioDeck::make('plan') ->options([...]) ->colors([ 'hobby' => 'gray', 'pro' => Color::Violet, 'team' => Color::hex('#0ea5e9'), ])
color() is only the fallback for options without their own color. A color from colors(), the options enum's HasColor, or getOptionColorFromRecordUsing() always wins. To force one color on every option, pass it to colors() instead:
RadioDeck::make('platform') ->options(Platform::class) ->colors('danger') // Overrides the enum's colors
Using enums
Pass an enum to options() and RadioDeck reads labels, descriptions, icons and colors from Filament's own enum contracts. The selected value is cast to the enum.
use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasDescription; use Filament\Support\Contracts\HasIcon; use Filament\Support\Contracts\HasLabel; use Filament\Support\Icons\Heroicon; enum Platform: string implements HasColor, HasDescription, HasIcon, HasLabel { case Ios = 'ios'; case Web = 'web'; public function getLabel(): string { return match ($this) { self::Ios => 'iOS', self::Web => 'Web', }; } public function getDescription(): string { return match ($this) { self::Ios => 'iOS mobile app', self::Web => 'Web app', }; } public function getIcon(): Heroicon { return match ($this) { self::Ios => Heroicon::OutlinedDevicePhoneMobile, self::Web => Heroicon::OutlinedGlobeAlt, }; } public function getColor(): string { return match ($this) { self::Ios => 'gray', self::Web => 'info', }; } }
RadioDeck::make('platform') ->options(Platform::class)
Explicit descriptions(), icons() and colors() arrays still take priority over the enum.
The
JaOcero\RadioDeck\Contracts\HasDescriptionsandHasIconscontracts (with->descriptions(Enum::class)and->icons(Enum::class)) still work, but are deprecated. Implement Filament'sHasDescriptionandHasIconinstead.
Multiple selection
multiple() renders checkboxes and stores an array. Cast the attribute to array (or an enum collection) on your model.
RadioDeck::make('regions') ->options([...]) ->multiple() ->minItems(1) ->maxItems(3)
Searching and bulk toggling
RadioDeck::make('regions') ->options([...]) ->multiple() ->searchable() ->bulkToggleable() // "Select all" and "Deselect all", multiple() only ->searchPrompt('Search regions') ->noSearchResultsMessage('No regions found.') ->searchDebounce(300)
Customize the bulk toggle actions with selectAllAction(fn (Action $action) => $action->label(...)) and deselectAllAction().
Integrating with an Eloquent relationship
For a BelongsTo relationship, name the field after the foreign key:
use App\Models\Category; RadioDeck::make('category_id') ->relationship('category', 'name') ->getOptionDescriptionFromRecordUsing(fn (Category $record): ?string => $record->description) ->getOptionIconFromRecordUsing(fn (Category $record): ?string => $record->icon) ->getOptionColorFromRecordUsing(fn (Category $record): ?string => $record->color)
For a BelongsToMany relationship, enable multiple(). The selection is synced to the pivot table, and pivotData() is saved with it:
RadioDeck::make('features') ->relationship(titleAttribute: 'name') ->multiple() ->pivotData(['note' => 'Added by an admin'])
relationship() also accepts a modifyQueryUsing closure, and getOptionLabelFromRecordUsing() customizes the labels.
Boolean options
RadioDeck::make('is_public') ->boolean('Public', 'Private') ->icons([1 => Heroicon::OutlinedLockOpen, 0 => Heroicon::OutlinedLockClosed])
Inline layout
inline() places the cards next to each other instead of in a grid:
RadioDeck::make('size') ->options([...]) ->inline()
In a grid, cards fill rows first. gridDirection(GridDirection::Column) fills columns first instead.
Tooltips
RadioDeck::make('plan') ->options([...]) ->tooltips([ 'enterprise' => 'Contact sales to unlock this plan.', ])
Selection indicator
Show a radio or checkbox style indicator in the corner of each card:
RadioDeck::make('plan') ->options([...]) ->selectionIndicator()
The indicator follows alignment(): vertically centered by default, at the top with Alignment::Start and at the bottom with Alignment::End. Stacked cards (with a flex-col class) keep it in the top corner. With the default padding, cards reserve space on the right for the indicator. If you set your own padding(), it's used as-is, so keep it symmetric for centered tiles.
Disabling options
RadioDeck::make('plan') ->options([...]) ->disableOptionWhen(fn (string $value): bool => $value === 'enterprise')
Submitted values are validated against the enabled options automatically.
Styling
Every part of the card has a stable CSS class you can target from your theme: fi-fo-radio-deck, fi-fo-radio-deck-option, fi-fo-radio-deck-card, fi-fo-radio-deck-icon, fi-fo-radio-deck-label, fi-fo-radio-deck-description and fi-fo-radio-deck-indicator.
Upgrading
From v1.x (Filament v3)
- Create a custom theme with
php artisan make:filament-theme. - Remove the plugin from the
contentarray of yourtailwind.config.jsand add the@sourcedirective shown above to your theme CSS. - Rename
->gap()to->optionsGap().
Deprecated enum contracts
Replace JaOcero\RadioDeck\Contracts\HasDescriptions::getDescriptions() and HasIcons::getIcons() with Filament's HasDescription::getDescription() and HasIcon::getIcon(). You can then drop the ->descriptions(Enum::class) and ->icons(Enum::class) calls, because ->options(Enum::class) picks them up.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
