errohitsinghal / yii2-tom-select
Tom Select widget for Yii2 Framework with Bootstrap 5 support
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:yii2-extension
pkg:composer/errohitsinghal/yii2-tom-select
Requires
- php: >=7.4
- yiisoft/yii2: ~2.0.45
- yiisoft/yii2-bootstrap5: ~2.0.0
Requires (Dev)
- phpunit/phpunit: ^9.5
README
A Yii2 widget for Tom Select - a dynamic, framework agnostic, and lightweight (~16kb gzipped) <select> UI control with autocomplete and native-feeling keyboard navigation.
Features
- ✅ Bootstrap 5 integration
- ✅ Local vendor files (no CDN dependencies)
- ✅ Full Tom Select API support
- ✅ All plugins included
- ✅ Model and non-model usage
- ✅ AJAX/Remote data loading
- ✅ Custom rendering templates
- ✅ Event handling
- ✅ Multi-select support
- ✅ Tagging/item creation
- ✅ Diacritics support
Requirements
- PHP >= 7.4
- Yii2 >= 2.0.45
- yii2-bootstrap5 >= 2.0.0
Installation
Install via Composer:
composer require errohitsinghal/yii2-tom-select
Quick Start
Basic Select
use errohitsinghal\tomselect\TomSelect; <?= TomSelect::widget([ 'model' => $model, 'attribute' => 'category_id', 'items' => [ '1' => 'Option 1', '2' => 'Option 2', '3' => 'Option 3', ], ]) ?>
Without Model
<?= TomSelect::widget([ 'name' => 'category', 'value' => 1, 'items' => [ '1' => 'Option 1', '2' => 'Option 2', '3' => 'Option 3', ], ]) ?>
Multi-Select
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'tags', 'items' => $tagList, 'multiple' => true, 'plugins' => ['remove_button', 'clear_button'], ]) ?>
Configuration Options
Widget Properties
| Property | Type | Default | Description |
|---|---|---|---|
type |
string | 'select' |
Input type: 'select' or 'text' |
items |
array | [] |
Option items for select element |
multiple |
bool | false |
Allow multiple selections |
placeholder |
string | null |
Placeholder text |
create |
bool/callable | false |
Allow creating new items |
maxItems |
int/null | 1 |
Maximum items (null for unlimited) |
maxOptions |
int/null | 50 |
Maximum options in dropdown |
plugins |
array | [] |
Tom Select plugins to enable |
clientOptions |
array | [] |
Tom Select configuration options |
clientEvents |
array | [] |
Event handlers |
useBaseAsset |
bool | false |
Use base asset (without plugins) |
hideSelected |
bool | null |
Hide selected items from dropdown |
closeAfterSelect |
bool | null |
Close dropdown after selection |
allowEmptyOption |
bool | false |
Allow empty option |
searchField |
string/array | null |
Fields to search in |
sortField |
string/array | null |
Fields to sort by |
valueField |
string | 'value' |
Property to use as value |
labelField |
string | 'text' |
Property to use as label |
diacritics |
bool | true |
Enable diacritics support |
loadThrottle |
int | 300 |
Load throttle in milliseconds |
preload |
bool/string | false |
Preload options (true, false, or 'focus') |
renderOption |
JsExpression | null |
Custom option render function |
renderItem |
JsExpression | null |
Custom item render function |
load |
JsExpression | null |
Remote data loading function |
variableName |
string | null |
JS variable name for instance |
Available Plugins
Tom Select comes with many built-in plugins:
| Plugin | Description |
|---|---|
caret_position |
Use ← and → arrow keys to move between items |
change_listener |
Update when underlying input changes |
checkbox_options |
Display checkboxes in dropdown |
clear_button |
Add button to clear all selections |
drag_drop |
Allow drag-n-drop sorting of items |
dropdown_header |
Add header to dropdown |
dropdown_input |
Move input to dropdown |
input_autogrow |
Auto-grow input width as user types |
no_active_items |
Disable selecting items |
no_backspace_delete |
Prevent backspace deletion |
optgroup_columns |
Display optgroups as columns |
remove_button |
Add remove button to each item |
restore_on_backspace |
Restore deleted items on backspace |
virtual_scroll |
Virtual scrolling for large lists |
Usage with ActiveForm
The widget integrates seamlessly with Yii2's ActiveForm. You have two options:
Option 1: Using the Widget Directly
use errohitsinghal\tomselect\TomSelect; use yii\bootstrap5\ActiveForm; <?php $form = ActiveForm::begin(); ?> <?= $form->field($model, 'category_id')->widget(TomSelect::class, [ 'items' => $categories, 'placeholder' => 'Select a category...', ]) ?> <?= $form->field($model, 'tags')->widget(TomSelect::class, [ 'items' => $tagList, 'multiple' => true, 'create' => true, 'plugins' => ['remove_button'], ]) ?> <?php ActiveForm::end(); ?>
Option 2: Using the ActiveField Extension
For a cleaner syntax, use the custom ActiveField class:
use errohitsinghal\tomselect\TomSelect; use errohitsinghal\tomselect\ActiveField; use yii\bootstrap5\ActiveForm; <?php $form = ActiveForm::begin([ 'fieldClass' => ActiveField::class, ]); ?> // Basic select <?= $form->field($model, 'category_id')->tomSelect($categories, [ 'placeholder' => 'Select a category...', ]) ?> // Multi-select with plugins <?= $form->field($model, 'tags')->tomSelect($tagList, [ 'multiple' => true, 'plugins' => ['remove_button', 'clear_button'], ]) ?> // Tagging field (pre-configured) <?= $form->field($model, 'keywords')->tomSelectTags() ?> // AJAX loading field <?= $form->field($model, 'user_id')->tomSelectAjax('/api/users/search', [ 'valueField' => 'id', 'labelField' => 'name', 'searchField' => ['name', 'email'], 'placeholder' => 'Search users...', ]) ?> <?php ActiveForm::end(); ?>
ActiveField Helper Methods
| Method | Description |
|---|---|
tomSelect($items, $options) |
Basic Tom Select field |
tomSelectTags($options) |
Pre-configured tagging field with remove buttons |
tomSelectAjax($url, $options) |
Pre-configured AJAX loading field |
Complete Form Example
<?php use errohitsinghal\tomselect\ActiveField; use yii\bootstrap5\ActiveForm; use yii\web\JsExpression; ?> <?php $form = ActiveForm::begin([ 'fieldClass' => ActiveField::class, ]); ?> <div class="row"> <div class="col-md-6"> <?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?> </div> <div class="col-md-6"> <?= $form->field($model, 'status')->tomSelect([ 'draft' => 'Draft', 'published' => 'Published', 'archived' => 'Archived', ]) ?> </div> </div> <div class="row"> <div class="col-md-6"> <?= $form->field($model, 'category_id')->tomSelect($categories, [ 'placeholder' => 'Select category...', 'allowEmptyOption' => true, ]) ?> </div> <div class="col-md-6"> <?= $form->field($model, 'tags')->tomSelectTags([ 'placeholder' => 'Add tags...', ]) ?> </div> </div> <div class="row"> <div class="col-md-6"> <?= $form->field($model, 'author_id')->tomSelectAjax('/api/users/search', [ 'placeholder' => 'Search author...', ]) ?> </div> <div class="col-md-6"> <?= $form->field($model, 'related_posts')->tomSelect($posts, [ 'multiple' => true, 'maxItems' => 5, 'plugins' => ['remove_button'], 'placeholder' => 'Select up to 5 related posts...', ]) ?> </div> </div> <div class="form-group"> <?= \yii\bootstrap5\Html::submitButton('Save', ['class' => 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?>
Form Validation Styling
Tom Select automatically integrates with Bootstrap 5 validation states. When a field has validation errors, the Tom Select control will display with the appropriate error styling.
// The widget respects Yii2's validation <?= $form->field($model, 'category_id')->tomSelect($categories) ?> // Custom error display <?= $form->field($model, 'category_id', [ 'errorOptions' => ['class' => 'invalid-feedback d-block'], ])->tomSelect($categories) ?>
Usage Examples
With Option Groups
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'vehicle', 'items' => [ 'Cars' => [ 'toyota' => 'Toyota', 'honda' => 'Honda', 'ford' => 'Ford', ], 'Motorcycles' => [ 'yamaha' => 'Yamaha', 'kawasaki' => 'Kawasaki', ], ], ]) ?>
Tagging (Create New Items)
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'tags', 'type' => TomSelect::TYPE_TEXT, 'multiple' => true, 'create' => true, 'plugins' => ['remove_button'], 'clientOptions' => [ 'persist' => false, 'createOnBlur' => true, ], ]) ?>
With Plugins and Options
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'users', 'items' => $userList, 'multiple' => true, 'plugins' => [ 'remove_button' => [ 'title' => 'Remove this item', ], 'clear_button' => [ 'title' => 'Remove all items', ], 'dropdown_header' => [ 'title' => 'Select Users', ], ], ]) ?>
AJAX Remote Data Loading
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'user_id', 'placeholder' => 'Search for a user...', 'clientOptions' => [ 'valueField' => 'id', 'labelField' => 'name', 'searchField' => ['name', 'email'], 'load' => new \yii\web\JsExpression('function(query, callback) { if (!query.length) return callback(); fetch("/api/users/search?q=" + encodeURIComponent(query)) .then(response => response.json()) .then(json => callback(json.results)) .catch(() => callback()); }'), ], ]) ?>
With Custom Rendering
use yii\web\JsExpression; <?= TomSelect::widget([ 'model' => $model, 'attribute' => 'country_id', 'items' => $countries, 'renderOption' => new JsExpression('function(data, escape) { return "<div class=\"option\">" + "<img src=\"/img/flags/" + escape(data.value) + ".png\" class=\"me-2\" width=\"20\">" + "<span>" + escape(data.text) + "</span>" + "</div>"; }'), 'renderItem' => new JsExpression('function(data, escape) { return "<div class=\"item\">" + "<img src=\"/img/flags/" + escape(data.value) + ".png\" class=\"me-2\" width=\"16\">" + escape(data.text) + "</div>"; }'), ]) ?>
Event Handling
use yii\web\JsExpression; <?= TomSelect::widget([ 'model' => $model, 'attribute' => 'category', 'items' => $categories, 'clientEvents' => [ 'change' => new JsExpression('function(value) { console.log("Selected:", value); // Trigger dependent dropdown update updateSubcategories(value); }'), 'item_add' => new JsExpression('function(value, item) { console.log("Added item:", value); }'), 'dropdown_open' => new JsExpression('function(dropdown) { console.log("Dropdown opened"); }'), ], ]) ?>
Virtual Scroll for Large Datasets
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'product_id', 'placeholder' => 'Search products...', 'plugins' => ['virtual_scroll'], 'maxOptions' => null, 'clientOptions' => [ 'valueField' => 'id', 'labelField' => 'name', 'searchField' => 'name', 'firstUrl' => new JsExpression('function(query) { return "/api/products?q=" + encodeURIComponent(query) + "&page=1"; }'), 'load' => new JsExpression('function(query, callback) { const url = this.getUrl(query); fetch(url) .then(response => response.json()) .then(json => { this.setNextUrl(query, json.next_url); callback(json.results); }) .catch(() => callback()); }'), ], ]) ?>
Using HTML Options
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'status', 'items' => $statuses, 'options' => [ 'id' => 'custom-id', 'class' => 'custom-class', 'data-custom' => 'value', ], ]) ?>
Dependent/Chained Dropdowns
// Parent dropdown <?= TomSelect::widget([ 'model' => $model, 'attribute' => 'country_id', 'items' => $countries, 'variableName' => 'countrySelect', 'clientEvents' => [ 'change' => new JsExpression('function(value) { citySelect.clear(); citySelect.clearOptions(); if (value) { citySelect.load(value); } }'), ], ]) ?> // Child dropdown <?= TomSelect::widget([ 'model' => $model, 'attribute' => 'city_id', 'items' => [], 'variableName' => 'citySelect', 'placeholder' => 'Select country first...', 'clientOptions' => [ 'valueField' => 'id', 'labelField' => 'name', 'load' => new JsExpression('function(countryId, callback) { fetch("/api/cities?country_id=" + countryId) .then(response => response.json()) .then(json => callback(json)) .catch(() => callback()); }'), ], ]) ?>
Pre-populating with Initial Options (AJAX)
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'user_id', 'clientOptions' => [ 'valueField' => 'id', 'labelField' => 'name', 'searchField' => ['name', 'email'], // Pre-populate with selected option 'options' => [ [ 'id' => $model->user_id, 'name' => $model->user->name, 'email' => $model->user->email, ], ], 'items' => [$model->user_id], 'load' => new JsExpression('function(query, callback) { if (!query.length) return callback(); fetch("/api/users?q=" + encodeURIComponent(query)) .then(response => response.json()) .then(json => callback(json)) .catch(() => callback()); }'), ], ]) ?>
API Methods
After initialization, you can access the Tom Select instance through the variable name:
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'category', 'items' => $categories, 'variableName' => 'mySelect', ]) ?> <script> // Clear selection mySelect.clear(); // Add option dynamically mySelect.addOption({value: 'new', text: 'New Option'}); // Select an item mySelect.addItem('new'); // Remove option mySelect.removeOption('value'); // Get current value var value = mySelect.getValue(); // Set value mySelect.setValue('newValue'); // Lock/disable mySelect.lock(); mySelect.disable(); // Unlock/enable mySelect.unlock(); mySelect.enable(); // Destroy instance mySelect.destroy(); </script>
Using Base Asset (Smaller Footprint)
If you don't need all plugins, use the base asset:
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'category', 'items' => $categories, 'useBaseAsset' => true, ]) ?>
Client Options Reference
All Tom Select options are supported via the clientOptions property. Common options include:
| Option | Type | Default | Description |
|---|---|---|---|
options |
array | [] |
Initial options list |
items |
array | [] |
Initial selected values |
create |
bool/function | false |
Allow creating new items |
createOnBlur |
bool | false |
Create item when leaving field |
createFilter |
RegExp/string/function | null |
Filter for new items |
delimiter |
string | ',' |
Delimiter for multiple values |
highlight |
bool | true |
Highlight matches |
persist |
bool | true |
Persist created items |
openOnFocus |
bool | true |
Open dropdown on focus |
maxOptions |
int | 50 |
Max options to display |
maxItems |
int | null |
Max items to select |
hideSelected |
bool | null |
Hide selected from dropdown |
closeAfterSelect |
bool | undefined |
Close after selection |
allowEmptyOption |
bool | false |
Allow empty option |
loadThrottle |
int | 300 |
Throttle for load requests |
loadingClass |
string | 'loading' |
Loading state CSS class |
placeholder |
string | undefined |
Placeholder text |
preload |
bool/string | false |
Preload options |
dropdownParent |
string | null |
Dropdown container |
addPrecedence |
bool | false |
"Add..." is default selection |
selectOnTab |
bool | false |
Select with tab key |
diacritics |
bool | true |
International character support |
duplicates |
bool | false |
Allow duplicate selections |
Events Reference
| Event | Arguments | Description |
|---|---|---|
initialize |
- | Control initialized |
change |
value | Value changed |
focus |
- | Control focused |
blur |
- | Control blurred |
item_add |
value, item | Item added |
item_remove |
value, item | Item removed |
item_select |
item | Item selected |
clear |
- | Selection cleared |
option_add |
value, data | Option added |
option_remove |
value | Option removed |
option_clear |
- | All options cleared |
optgroup_add |
id, data | Option group added |
optgroup_remove |
id | Option group removed |
optgroup_clear |
- | All option groups cleared |
dropdown_open |
dropdown | Dropdown opened |
dropdown_close |
dropdown | Dropdown closed |
type |
str | User typing |
load |
data | Options loaded |
destroy |
- | Control destroyed |
Styling
The widget uses Bootstrap 5 themed CSS by default. You can customize the appearance:
Override CSS Variables
.ts-wrapper { --ts-pr-clear-button: 0; --ts-pr-caret: 0; --ts-pr-min: .75rem; } .ts-control { border-radius: 0.375rem; } .ts-dropdown { border-radius: 0.375rem; }
Custom CSS Classes
<?= TomSelect::widget([ 'model' => $model, 'attribute' => 'category', 'items' => $categories, 'options' => [ 'class' => 'my-custom-select', ], ]) ?>
Troubleshooting
Widget not initializing
Make sure jQuery is loaded if you're using it elsewhere. Tom Select doesn't require jQuery but may conflict if jQuery plugins modify the DOM.
Options not showing
Check that your items array is properly formatted:
'items' => [ 'value1' => 'Label 1', 'value2' => 'Label 2', ]
AJAX not working
Ensure your endpoint returns JSON in the expected format:
[
{"id": 1, "name": "Option 1"},
{"id": 2, "name": "Option 2"}
]
And configure valueField and labelField to match your data structure.
Multiple selection not working
Make sure multiple is set to true:
'multiple' => true,
Support
If you find this package helpful, consider buying me a coffee! ☕
License
This package is open-sourced software licensed under the MIT License.
Credits
- Tom Select - The underlying JavaScript library
- Yii2 Framework - The PHP framework
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Changelog
1.0.0
- Initial release
- Full Tom Select API support
- Bootstrap 5 integration
- All plugins included
- Comprehensive documentation