codewithdennis/filament-advanced-choice
Adds 8 advanced Filament form fields: radio- and checkbox-based card/stacked card variants plus enhanced CheckboxList with descriptions and extras. Supports Filament v4/v5 and integrates into custom themes via @source for proper styling.
Eight form fields that turn a plain radio or checkbox list into something worth clicking: cards, stacked cards, tables and richer lists, each able to carry a description and an extra column of information.
Four fields extend Filament's Radio for single choice, four extend CheckboxList for multiple choice. They inherit the full API of the field they extend, so searchable(), bulkToggleable(), disableOptionWhen(), validation and state handling all work exactly as you already know them.
use CodeWithDennis\FilamentAdvancedChoice\Filament\Forms\Components\RadioCard;
RadioCard::make('plan')
->options([
'hobby' => 'Hobby',
'pro' => 'Pro',
'team' => 'Team',
])
->descriptions([
'hobby' => 'For side projects and experiments.',
'pro' => 'For freelancers shipping client work.',
'team' => 'For teams that need shared billing.',
])
->extras([
'hobby' => 'Free',
'pro' => '$29 / month',
'team' => '$99 / month',
]);
Screenshots of every layout are at the bottom of this page.
1. Install the package:
composer require codewithdennis/filament-advanced-choice
2. Register the views as a Tailwind source in your custom Filament theme, so the utility classes these fields use end up in your CSS:
@source '../../../../vendor/codewithdennis/filament-advanced-choice/resources/**/*.blade.php';
3. Rebuild the theme:
npm run build
[!IMPORTANT] Skipping step 2 is the most common reason fields render unstyled. If a field looks like a plain list of checkboxes, the theme has not picked up these views yet. The same applies after upgrading: run
npm run buildagain so new utility classes are compiled.
All eight live in CodeWithDennis\FilamentAdvancedChoice\Filament\Forms\Components.
| Field | Selection | Layout |
|---|---|---|
RadioList |
Single | Vertical list |
RadioTable |
Single | Table, one option per row |
RadioCard |
Single | Grid of cards, 3 columns by default |
RadioStackedCard |
Single | Cards stacked full width |
CheckboxList |
Multiple | Vertical list |
CheckboxTable |
Multiple | Table, one option per row |
CheckboxCard |
Multiple | Grid of cards, 3 columns by default |
CheckboxStackedCard |
Multiple | Cards stacked full width |
[!NOTE] The plural class names (
RadioCards,RadioStackedCards,CheckboxCards,CheckboxStackedCards) still exist as deprecated aliases. Use the singular names in new code.
Every field takes the same three pieces of content, all keyed by option value.
| Method | Shows as |
|---|---|
options() |
The label |
descriptions() |
A subtitle under the label |
extras() |
A trailing column, ideal for a price, count or hint |
descriptions() and extras() are optional and can be used independently.
use CodeWithDennis\FilamentAdvancedChoice\Filament\Forms\Components\CheckboxStackedCard;
CheckboxStackedCard::make('delivery_type')
->options([
'standard' => 'Standard delivery',
'express' => 'Express delivery',
'overnight' => 'Overnight delivery',
])
->descriptions([
'standard' => 'Arrives within 5 to 7 business days.',
'express' => 'Arrives within 2 to 3 business days.',
'overnight' => 'Arrives the next business day.',
])
->extras([
'standard' => '$5.00',
'express' => '$10.00',
'overnight' => '$20.00',
]);
Pass a backed enum to options() and every field reads its content from the enum itself. Implement the contract for each piece you need:
| Contract | Provides |
|---|---|
Filament\Support\Contracts\HasLabel |
The label |
Filament\Support\Contracts\HasDescription |
The description |
CodeWithDennis\FilamentAdvancedChoice\Filament\Interfaces\HasExtra |
The extras column |
Filament\Support\Contracts\HasColor |
A colour for that single option, overriding the field colour. Honoured by the four Radio based fields only. |
CheckboxStackedCard::make('delivery_type')
->options(DeliveryType::class);
<?php
declare(strict_types=1);
namespace App\Enums;
use CodeWithDennis\FilamentAdvancedChoice\Filament\Interfaces\HasExtra;
use Filament\Support\Contracts\HasDescription;
use Filament\Support\Contracts\HasLabel;
enum DeliveryType: string implements HasDescription, HasExtra, HasLabel
{
case Standard = 'standard';
case Express = 'express';
case Overnight = 'overnight';
public function getLabel(): string
{
return match ($this) {
self::Standard => __('Standard delivery'),
self::Express => __('Express delivery'),
self::Overnight => __('Overnight delivery'),
};
}
public function getDescription(): string
{
return match ($this) {
self::Standard => __('Arrives within 5 to 7 business days.'),
self::Express => __('Arrives within 2 to 3 business days.'),
self::Overnight => __('Arrives the next business day.'),
};
}
public function getExtra(): ?string
{
return match ($this) {
self::Standard => __('$5.00'),
self::Express => __('$10.00'),
self::Overnight => __('$20.00'),
};
}
}
You can still override individual pieces: extras() passed explicitly wins over getExtra() on the cases.
These come straight from Filament's Radio and CheckboxList, unchanged.
Available on all eight fields. The search box filters on labels and descriptions.
CheckboxTable::make('delivery_type')
->options(DeliveryType::class)
->searchable()
->searchPrompt('Search delivery types...')
->noSearchResultsMessage('No delivery type matches your search.');
Multiple choice fields only.
CheckboxCard::make('delivery_type')
->options(DeliveryType::class)
->bulkToggleable();
Disabled options are dimmed, unclickable and keep the not-allowed cursor.
CheckboxList::make('delivery_type')
->options(DeliveryType::class)
->disableOptionWhen(fn (string $value): bool => $value === 'overnight');
Multiple choice fields only.
CheckboxCard::make('delivery_type')
->options(DeliveryType::class)
->minItems(1)
->maxItems(3);
The card layouts arrange their options in a grid. RadioCard and CheckboxCard default to three columns, CheckboxStackedCard to one. Pass a number, or an array keyed by breakpoint.
RadioCard::make('delivery_type')
->options(DeliveryType::class)
->columns(4);
RadioCard::make('delivery_type')
->options(DeliveryType::class)
->columns([
'default' => 1,
'md' => 2,
'xl' => 4,
]);
Use gridDirection(GridDirection::Column) to fill the grid top to bottom instead of left to right.
Every field is primary by default. Any Filament colour works.
use Filament\Support\Colors\Color;
CheckboxCard::make('delivery_type')
->options(DeliveryType::class)
->color(Color::Rose);
The whole option is clickable, so the checkbox or radio dot is often redundant. hiddenInputs() removes it while keeping the option selectable and accessible: a transparent input is stretched across the option instead.
CheckboxCard::make('delivery_type')
->options(DeliveryType::class)
->hiddenInputs();
Supported by the card and list layouts. RadioTable and CheckboxTable always show their native inputs and ignore this method.
On the four card layouts you can pair it with hiddenInputIcon(), which marks the selected card with an icon in its top right corner:
RadioCard::make('delivery_type')
->options(DeliveryType::class)
->hiddenInputs()
->hiddenInputIcon('heroicon-s-check-circle');
[!NOTE] There is no default icon. Without
hiddenInputIcon(), a selected card is marked by its outline alone.
visibleInputs() reverses hiddenInputs().
Because the entire option is clickable, all eight fields show a pointer cursor by default. Opt out per field with defaultCursor():
CheckboxCard::make('delivery_type')
->options(DeliveryType::class)
->defaultCursor();
cursorPointer() reverses defaultCursor(). Both accept a boolean or a Closure, so the cursor can follow other state:
RadioCard::make('delivery_type')
->options(DeliveryType::class)
->defaultCursor(fn (): bool => ! auth()->user()->canChooseDelivery());
Disabled options keep the not-allowed cursor either way.
A single choice field driven by an enum, laid out as cards without native inputs:
use App\Enums\DeliveryType;
use CodeWithDennis\FilamentAdvancedChoice\Filament\Forms\Components\RadioCard;
use Filament\Schemas\Schema;
use Filament\Support\Colors\Color;
public static function configure(Schema $schema): Schema
{
return $schema
->components([
RadioCard::make('delivery_type')
->label('How should we ship this?')
->options(DeliveryType::class)
->default(DeliveryType::Standard->value)
->required()
->columns(3)
->color(Color::Indigo)
->hiddenInputs()
->hiddenInputIcon('heroicon-s-check-circle')
->columnSpanFull(),
]);
}
Each example below uses the same options, descriptions and extras, so the layouts can be compared directly.
Contributions and pull requests are always welcome. If you want to discuss a bigger idea first, open an issue, but you do not have to. Running composer format before you open a PR helps keep CI green.
Report suspected vulnerabilities per .github/SECURITY.md. Do not post exploit details in a public issue.
This package is released under the MIT License. The complete terms are in LICENSE.
How can I help you explore Laravel packages today?