contao-components/choices
Contao Components Choices adds the Choices.js library integration for Contao CMS, enhancing select boxes and input fields with searchable, taggable, and multi-select UI features. Ideal for modern form UX with minimal setup.
Installation Add the package via Composer:
composer require contao-components/choices
Publish the assets (if needed):
php artisan vendor:publish --provider="ContaoComponents\Choices\ChoicesServiceProvider" --tag=public
Basic Usage Include the Choices.js bundle in your Blade template:
@choicesJs
Initialize Choices on an element:
const choices = new Choices('#my-select-element', {
removeItemButton: true,
placeholder: true,
placeholderValue: 'Select an option...',
});
First Use Case
Replace a standard <select> with a searchable, multi-select dropdown:
<select name="tags" multiple>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
Initialize with:
new Choices('#tags', { removeItemButton: true });
Dynamic Initialization Use Laravel’s Blade directives or Alpine.js to initialize Choices conditionally:
<select x-data="{ choices: null }" x-init="choices = new Choices($refs.select)">
@foreach($options as $option)
<option value="{{ $option->id }}">{{ $option->name }}</option>
@endforeach
</select>
Integration with Forms Bind Choices to Laravel Form requests:
public function store(Request $request) {
$validated = $request->validate([
'tags' => 'required|array',
'tags.*' => 'exists:tags,id',
]);
// Process $validated['tags'] (array of selected IDs)
}
Reactive Updates Update Choices dynamically via AJAX:
fetch('/api/search')
.then(response => response.json())
.then(data => {
choices.setChoices(data.map(item => ({ value: item.id, label: item.name })));
});
Laravel Collections Integration Convert a Laravel Collection to Choices-compatible format:
$options = $items->map(fn ($item) => [
'value' => $item->id,
'label' => $item->name,
'disabled' => $item->is_disabled,
]);
__() helper:
new Choices('#select', {
searchPlaceholderValue: '@lang("Select an option...")',
});
if (typeof window !== 'undefined') {
new Choices('#select');
}
Duplicate Initialization Avoid re-initializing Choices on the same element. Store instances in a global object:
window.choicesInstances = window.choicesInstances || {};
window.choicesInstances.mySelect = new Choices('#my-select');
Memory Leaks Destroy instances when no longer needed (e.g., on tab/unmount):
choices.destroy();
Laravel Mix/Vite Conflicts Ensure Choices.js is not bundled twice. Exclude it from auto-imports if using Vite:
// vite.config.js
optimizeDeps: { exclude: ['choices.js'] },
CSRF Token Mismatch When submitting forms with Choices, ensure the CSRF token is included:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
Choices is not defined (missing @choicesJs directive).value and label).choices.on('change') to debug dynamic updates:
choices.on('change', (event) => {
console.log('Selected:', event.detail.selected);
});
Custom Templates Override Choices templates via CSS classes or inline styles:
new Choices('#select', {
classNames: {
containerOuter: 'my-custom-container',
},
});
Laravel Service Provider Extend the package’s default config:
// config/choices.php
'default_options' => [
'searchEnabled' => true,
'shouldSortItems' => false,
],
Alpine.js Integration
Use Alpine’s x-model to sync Choices with Laravel models:
<select x-model="form.tags" x-init="new Choices($refs.select)">
@foreach($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->name }}</option>
@endforeach
</select>
How can I help you explore Laravel packages today?