xylo / multiselect-two
A Filament two-sided multiselect field with search, barcode scan, descriptions, and a natural accent theme. Compatible with Filament 4 and 5.
Requires
- php: ^8.2|^8.3|^8.4
- filament/filament: ^4.0 || ^5.0
- filament/forms: ^4.0 || ^5.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0 || ^10.0
- pestphp/pest: ^3.0 || ^4.0
- pestphp/pest-plugin-laravel: ^3.0 || ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Filament form field for transferring items between two lists — available and selected — with search, barcode scanning, descriptions, count badges, and a natural green accent.
Compatible with Filament 4.x and 5.x.
Installation
composer require xylo/multiselect-two
If you install from a local path:
{
"repositories": [
{
"type": "path",
"url": "../xylo-multiselect-two"
}
]
}
composer require xylo/multiselect-two:@dev
Publish assets (recommended after install / update):
php artisan filament:assets
Optional translations:
php artisan vendor:publish --tag="xylo-multiselect-two-translations"
Usage
use Xylo\MultiselectTwo\Forms\Components\XyloMultiselectTwo; XyloMultiselectTwo::make('menu_item_ids') // Optional — omit both (and use hiddenLabel) if you want no title/subtitle ->label('STEP 2 · Choose the items in this program') ->helperText('A punch is earned when a customer buys any item on the right.') ->selectableLabel('Available items') ->selectedLabel('In this program') ->options([ '1' => 'Cheese Burger 6"', '2' => 'Beef Burger', '3' => 'Chicken Burger', '4' => 'Veggie Burger', '5' => 'Fish Burger', '6' => 'Double Cheese', '7' => 'Classic Fries', '8' => 'Onion Rings', ]) ->descriptions([ '1' => '$7.99', '2' => '$8.99', '3' => '$7.49', '4' => '$6.99', '5' => '$8.49', '6' => '$9.99', '7' => '$3.49', '8' => '$4.29', ]) ->barcodes([ '1' => '100100', '2' => '100200', '3' => '100300', ]) ->skus([ '1' => 'CH-06', '2' => 'BF-01', ]) ->accentColor('#2f9e7a') // or 'natural' / 'primary' / any CSS color ->default(['2', '7']);
Without title / subtitle:
XyloMultiselectTwo::make('menu_item_ids') ->hiddenLabel() ->options($items) ->accentColor('#0ea5e9');
Rich options (label + description + barcode + sku in one array)
XyloMultiselectTwo::make('items') ->options([ '1' => [ 'label' => 'Cheese Burger 6"', 'description' => '$7.99', 'barcode' => '100100', 'sku' => 'CH-06', ], '2' => [ 'label' => 'Beef Burger', 'description' => '$8.99', 'barcode' => '100200', ], ]);
Features
- Two-sided transfer UI (available ↔ selected)
- Count badges on both panels
- Barcode scan input (Enter to add)
- Search / filter on both sides (name, SKU, barcode) with 200ms debounce
- Scroll windowing — renders
pageSizerows at a time (default 80), loads more on scroll - Prebuilt option list +
Setmembership for fast filtering on large catalogs - Dynamic options remount via
wire:key(safe for dependent fields like Free Reward ⊆ Products to Buy) - Item title + subtitle (e.g. price)
- Chevron move actions + remove (X) on selected rows
- Add all shown / Remove all (Add all adds every filtered available item, not only the first rendered page)
- Soft accent color only on hover (items + bulk buttons)
- Theme-aware colors: text/borders/surfaces follow Filament gray tokens + light/dark automatically
- Accent:
natural, Filament colors (primary,success, …), or any custom CSS color - Optional Filament
label()/helperText()— nothing forced by the package - Works with Filament
live(),disabled(), validation, defaults - English + Arabic translations
- Multiple independent instances on the same form (e.g. Products to Buy + Free Reward)
Configuration API
| Method | Description |
|---|---|
options() |
Available choices |
descriptions() |
Subtitle under each label |
barcodes() |
Values matched by the scanner |
skus() |
Included in search + scan |
selectableLabel() / selectedLabel() |
Panel titles |
enableSearch() / searchable() |
Toggle search inputs |
enableBarcode() / barcodeScanner() |
Toggle barcode input |
showCounts() |
Toggle count badges |
showBulkActions() |
Toggle footer buttons |
listHeight(320) |
Scroll area height in px |
pageSize(80) |
Rows rendered per chunk (scroll loads more); keep low for huge catalogs |
limitTo('other_field') |
Client-side: only show options selected on another field (no Livewire wait) |
accentColor(...) |
natural, primary/success/danger/warning/info/gray, or CSS color |
hoverColor(...) |
Shared hover for items + both buttons |
itemsHoverColor(...) |
Shared hover for both panels' rows |
availableHoverColor(...) / selectedHoverColor(...) |
Hover per panel |
buttonsHoverColor(...) |
Shared hover for Add all + Remove all |
addAllHoverColor(...) / removeAllHoverColor(...) |
Hover per button |
buttonsBackgroundColor(...) |
Shared default background for both buttons |
addAllBackgroundColor(...) / removeAllBackgroundColor(...) |
Background per button (defaults kept if unset) |
addAllLabel() / removeAllLabel() |
Footer button labels |
availableSearchPlaceholder() / selectedSearchPlaceholder() / barcodePlaceholder() |
Input placeholders |
label() / helperText() / hiddenLabel() |
Optional title & subtitle (Filament) |
Color priority (most specific wins):
- Per-part (
availableHoverColor,addAllHoverColor, …) - Group (
itemsHoverColor,buttonsHoverColor) - Shared (
hoverColor) - Default from
accentColor
// One color for everything on hover ->hoverColor('#dcfce7') // Cards together, buttons together ->itemsHoverColor('#ecfdf5') ->buttonsHoverColor('#fef3c7') // Fully separate ->availableHoverColor('#d1fae5') ->selectedHoverColor('#e0f2fe') ->addAllHoverColor('#bbf7d0') ->removeAllHoverColor('#fecaca') ->addAllBackgroundColor('#f3f4f6') // optional — default gray if omitted ->removeAllBackgroundColor('#ffffff') // optional — default card white if omitted
Dependent fields (Products to Buy + Free Reward)
Use two separate fields. Keep Free Reward options as a subset of Products to Buy, and prune invalid rewards when qualifying changes:
XyloMultiselectTwo::make('punch_card.qualifying_product_ids') ->label('Products to Buy') ->live(debounce: 400) ->showBulkActions(true) ->listHeight(260) ->pageSize(80) ->accentColor('success') ->barcodeScanner(false) ->options(fn (): array => $this->productTransferOptions()) ->afterStateUpdated(function ($state, Set $set, Get $get): void { $allowed = array_map('strval', $state ?? []); $rewards = array_map('strval', $get('punch_card.reward_product_ids') ?? []); $set('punch_card.reward_product_ids', array_values(array_intersect($rewards, $allowed))); }), XyloMultiselectTwo::make('punch_card.reward_product_ids') ->label('Free Reward Products') ->live(debounce: 400) ->showBulkActions(false) ->listHeight(220) ->pageSize(80) ->barcodeScanner(false) ->disabled(fn (Get $get): bool => blank($get('punch_card.qualifying_product_ids'))) ->options(fn (Get $get): array => $this->productTransferOptions( array_map('strval', $get('punch_card.qualifying_product_ids') ?? []) )),
The package remounts Alpine when option keys change (wire:key), so the Free Reward list refreshes when Products to Buy changes.
Example with labels from the screenshots
XyloMultiselectTwo::make('program_items') ->selectableLabel('Available items') ->selectedLabel('In this program') ->availableSearchPlaceholder('Search by name, SKU or barcode') ->selectedSearchPlaceholder('Filter included items') ->barcodePlaceholder('Scan barcode & press Enter') ->addAllLabel('Add all shown') ->removeAllLabel('Remove all') ->accentColor('#16a34a') ->options($items) ->descriptions($prices) ->barcodes($barcodes);
Accent examples:
->accentColor('natural') // package soft green ->accentColor('primary') // follows Filament panel primary theme ->accentColor('success') // Filament success palette ->accentColor('#0ea5e9') // any hex / CSS color
Structural colors (titles, prices, borders, cards, inputs) always use Filament --gray-* tokens and switch with light/dark mode — no extra config.
Version compatibility
| Package | Filament | PHP |
|---|---|---|
| 1.x | 4.x / 5.x | 8.2 / 8.3 / 8.4 |
Filament 5 keeps the Forms API from 4 and mainly upgrades Livewire; this package targets both.
Credits
- Developer: Qusai Homadi
- Package: Xylo (
xylo/multiselect-two)
License
MIT