A jQuery plugin for Bootstrap 5 that transforms a <select multiple> into a searchable, nested dropdown with checkboxes, accordion groups, and unlimited nesting depth.
Parents have their own value. Pre-selected items via selected: true. Houston and all of Canada are disabled: true. Smart button text shows "N selected" when more than 3 items are chosen. Try searching and using "Select all" — it only affects visible items.
Initialized from native <optgroup> HTML. Strawberry and Blueberry are pre-selected. Lime is disabled. The entire Tropical group is disabled.
Deep nesting with parent values at every level. "Computers" group starts pre-selected via selected: true.
Dark mode is handled automatically via Bootstrap 5.3's data-bs-theme="dark" on the <html> element. No extra CSS needed. Includes a disabled option (Coral).
<!-- CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="src/bootstrap-multiselect.css" rel="stylesheet">
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="src/bootstrap-multiselect.js"></script>
<select id="mySelect" multiple></select>
$('#mySelect').multiselect({
data: [
{
label: 'Group 1', value: 'grp1', // parents can have values too
children: [
{
label: 'Subgroup 1.1', value: 'sub1',
children: [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b', selected: true },
{ label: 'Option C', value: 'c', disabled: true }
]
},
{ label: 'Option D', value: 'd' }
]
},
{
label: 'Group 2', value: 'grp2', disabled: true, // disables entire group
children: [
{ label: 'Option E', value: 'e' },
{ label: 'Option F', value: 'f' }
]
}
]
});
<select id="mySelect" multiple>
<optgroup label="Group 1">
<option value="a">Option A</option>
<option value="b">Option B</option>
</optgroup>
<optgroup label="Group 2">
<option value="c" selected>Option C</option>
</optgroup>
</select>
$('#mySelect').multiselect();
// Get selected values (returns array — includes parent values when selected)
var values = $('#mySelect').val();
// The hidden <select> stays in sync, so .val() always works
// For programmatic selection, use the plugin API methods below
| Option | Type | Default | Description |
|---|---|---|---|
data |
Array|null |
null |
JS data provider array. Each item: { label, value?, children?, selected?, disabled? }. Items with children become groups; items without become selectable leaves. Parents can have a value which is included in .val() when selected. Use selected: true to pre-select (cascades to children). Use disabled: true to disable items (cascades to children for groups). If null, parses from the <select> DOM (respects disabled attribute on <option> and <optgroup>). |
placeholder |
string |
'Select...' |
Text shown in the display bar when nothing is selected. |
enableSearch |
boolean |
true |
Show or hide the search input at the top of the dropdown. |
searchPlaceholder |
string |
'Search...' |
Placeholder text for the search input. |
selectAll |
boolean |
true |
Show or hide the "Select all" checkbox. |
selectAllText |
string |
'Select all' |
Label for the select-all checkbox. |
selectAllJustVisible |
boolean |
true |
When true and a search is active, "Select all" only selects/deselects the visible (filtered) options. When false, it always affects all options regardless of search. |
collapseAll |
boolean |
true |
Whether all groups start collapsed. Set to false to expand all groups on init. |
maxHeight |
number |
250 |
Maximum height (px) of the scrollable options area. |
numberDisplayed |
number |
3 |
Maximum number of selected items to show as individual labels. When the count exceeds this, the display switches to nSelectedText. |
nSelectedText |
string |
'# selected' |
Text shown when more than numberDisplayed items are selected. # is replaced with the count. |
allSelectedText |
string |
'All selected' |
Text shown when every option is selected. Set to '' (empty) to disable and fall through to nSelectedText instead. |
onChange |
function|null |
null |
Callback fired on any selection change. Receives the array of selected values: function(selectedValues) {}. |
| Method | Arguments | Description |
|---|---|---|
$('#el').multiselect('select', values) |
values — string or array of values |
Programmatically select one or more options by value (works for both leaf and parent values). Updates the UI, hidden select, and triggers change. Parent checkboxes update automatically (checked if all children selected, indeterminate if partial). |
$('#el').multiselect('deselect', values) |
values — string or array of values |
Programmatically deselect one or more options by value. |
$('#el').multiselect('selectAll') |
none | Select every option in the tree. |
$('#el').multiselect('deselectAll') |
none | Deselect every option in the tree. |
$('#el').multiselect('expand') |
none | Expand all groups at every nesting level. |
$('#el').multiselect('expand', values) |
values — string or array of group values |
Expand specific group(s) by value. Only the named groups are expanded; their children remain in their current state. |
$('#el').multiselect('collapse') |
none | Collapse all groups at every nesting level. |
$('#el').multiselect('collapse', values) |
values — string or array of group values |
Collapse specific group(s) by value. |
$('#el').multiselect('getSelected') |
none | Returns an array of selected values (leaves and parents with values). Equivalent to $('#el').val(). |
$('#el').multiselect('refresh') |
none | Tears down and rebuilds the widget. Use after changing data externally or modifying the underlying <select> DOM. |
$('#el').multiselect('destroy') |
none | Removes the widget entirely: deletes generated DOM, shows the original <select>, unbinds all events, and clears stored instance data. |
$('#el').multiselect('enable') |
none | Re-enables a disabled widget. |
$('#el').multiselect('disable') |
none | Disables the widget (grays out, closes dropdown, prevents interaction). |
$('#el').multiselect('disableOptions', values) |
values — string or array of values |
Disable specific options or groups by value. Disabled groups cascade to all children. Disabled items cannot be selected/deselected and are excluded from parent state calculations. |
$('#el').multiselect('enableOptions', values) |
values — string or array of values |
Re-enable previously disabled options or groups by value. Cascades to children for groups. |
All classes use the bsms- prefix to avoid collisions. Override any of these in your own stylesheet to customize the look and feel.
| Class | Element | Description |
|---|---|---|
.bsms-container |
Outer wrapper <div> |
Root container. Has position: relative and width: 100%. Add a fixed width here to size the widget. |
.bsms-display |
Top bar / button | Styled like Bootstrap's .form-select. Shows selected items text. Uses overflow: hidden; text-overflow: ellipsis; white-space: nowrap to truncate when overflowing. |
.bsms-display-text |
<span> inside display |
The text content inside the display bar. Override to change font, color, or truncation behavior. |
.bsms-dropdown |
Dropdown panel | The dropdown menu. Absolutely positioned below the display bar. Has z-index: 1050, box shadow, and border. Toggle visibility via .show. |
.bsms-options |
Scrollable options area | Contains all group/leaf nodes. Has max-height (configurable via maxHeight option) and overflow-y: auto. |
| Class | Element | Description |
|---|---|---|
.bsms-search-wrap |
Search input wrapper | Contains the search <input>. Has bottom margin for spacing. |
.bsms-search |
Search <input> |
The text input. Uses Bootstrap .form-control.form-control-sm. |
.bsms-select-all |
Select-all row | Container for the expand-all arrow and "Select all" checkbox. Has a bottom border separator and bold label. |
.bsms-expand-all |
Expand/collapse all arrow | Arrow to the left of "Select all" that toggles all groups open/closed. Uses the same .bsms-arrow styling. |
.bsms-no-results |
"No results" message | Shown when search matches nothing. Hidden by default; gets .show when active. Override to change the message style. |
| Class | Element | Description |
|---|---|---|
.bsms-group |
Group wrapper | Contains the group header and its children container. Has a data-node-id attribute for identification. |
.bsms-group-header |
Group header row | Flexbox row containing the arrow, checkbox, and label. Has a light gray background (#f8f9fa). Override background to change group header appearance. |
.bsms-arrow |
Accordion toggle arrow | The expand/collapse triangle. Rotates 90 degrees when expanded. Has class .collapsed when the group is closed. Override transition to change animation speed, or replace the content to use a different icon. |
.bsms-group-children |
Children container | Hidden by default (display: none). Gets .show class to expand (display: block). Contains nested .bsms-group or .bsms-leaf elements. |
| Class | Element | Description |
|---|---|---|
.bsms-leaf |
Leaf option row | A selectable option with a checkbox. Has a data-node-id attribute. Even-indexed leaves get a light gray background for zebra striping. |
.bsms-leaf.selected |
Selected leaf | Applied when the option is checked. Default: light blue background (#e7f1ff). Override to change the selected highlight color. |
.bsms-check |
All checkboxes | Applied to every checkbox (groups, leaves, select-all). Uses Bootstrap .form-check-input. The browser handles indeterminate rendering natively when el.indeterminate = true. |
| Class | Applied To | Description |
|---|---|---|
.show |
.bsms-dropdown, .bsms-group-children, .bsms-no-results |
Toggles visibility. The dropdown panel and group children use this to expand/collapse. |
.collapsed |
.bsms-arrow |
Present when the group is collapsed (arrow points right). Removed when expanded (arrow rotates 90 degrees to point down). |
.bsms-hidden |
Any node | Applied during search filtering to hide non-matching nodes. Uses display: none !important. |
.disabled |
.bsms-container |
Applied when the widget is disabled via disable(). Grays out the display bar and blocks pointer events. |
.bsms-disabled |
.bsms-leaf, .bsms-group-header |
Applied to individually disabled options and group headers. Reduces opacity and blocks checkbox interaction. On disabled groups, the accordion arrow remains clickable. |
The plugin automatically honors Bootstrap 5.3's dark mode. Just set data-bs-theme="dark" on your <html> element — no extra CSS needed. All colors use Bootstrap CSS variables (--bs-body-bg, --bs-border-color, --bs-tertiary-bg, etc.) that adapt automatically.
/* Enable dark mode globally */
document.documentElement.setAttribute('data-bs-theme', 'dark');
/* Or toggle it */
var current = document.documentElement.getAttribute('data-bs-theme');
document.documentElement.setAttribute('data-bs-theme', current === 'dark' ? 'light' : 'dark');
/* Custom arrow icon (FontAwesome example) */
.bsms-arrow::before {
content: '\f054'; /* fa-chevron-right */
font-family: 'Font Awesome 6 Free';
font-weight: 900;
}
.bsms-arrow:not(.collapsed)::before {
content: '\f078'; /* fa-chevron-down */
}
/* Wider dropdown */
.bsms-container {
max-width: 500px;
}
/* Taller scroll area */
.bsms-options {
max-height: 400px;
}
The plugin generates this DOM structure after the hidden <select>. Understanding this helps when writing custom CSS or attaching external event handlers.
<select id="mySelect" multiple style="display:none">
<!-- Auto-synced: one <option> per leaf + parents with values, selected state mirrors UI -->
</select>
<div class="bsms-container" tabindex="0">
<div class="bsms-display form-select">
<span class="bsms-display-text">Option A, Option B</span>
</div>
<div class="bsms-dropdown">
<div class="bsms-search-wrap">
<input class="form-control form-control-sm bsms-search">
</div>
<div class="bsms-select-all d-flex align-items-center">
<span class="bsms-arrow bsms-expand-all collapsed">▶</span>
<div class="form-check ms-1">
<input class="form-check-input" type="checkbox">
<label class="form-check-label">Select all</label>
</div>
</div>
<div class="bsms-options">
<!-- Group node -->
<div class="bsms-group" data-node-id="bsms-0-0-0">
<div class="bsms-group-header">
<span class="bsms-arrow collapsed">▶</span>
<div class="form-check ms-1">
<input class="form-check-input bsms-check" type="checkbox">
<label class="form-check-label">Group Name</label>
</div>
</div>
<div class="bsms-group-children">
<!-- Leaf node -->
<div class="bsms-leaf" data-node-id="bsms-0-1-0">
<div class="form-check">
<input class="form-check-input bsms-check" type="checkbox" value="a">
<label class="form-check-label">Option A</label>
</div>
</div>
<!-- More leaves or nested groups... -->
</div>
</div>
<div class="bsms-no-results">No results found</div>
</div>
</div>
</div>