Bootstrap Multiselect

A jQuery plugin for Bootstrap 5 that transforms a <select multiple> into a searchable, nested dropdown with checkboxes, accordion groups, and unlimited nesting depth.

Live Demos

3-Level Nesting with Parent Values

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.

Selection:
Expand/Collapse:
Enable/Disable:

                    
Simple Optgroup (DOM-based)

Initialized from native <optgroup> HTML. Strawberry and Blueberry are pre-selected. Lime is disabled. The entire Tropical group is disabled.


                    
4-Level Deep Nesting

Deep nesting with parent values at every level. "Computers" group starts pre-selected via selected: true.

Selection:
Expand/Collapse:

                    
Additional Example (try toggling dark mode!)

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).


                    

Quickstart

1. Include Dependencies
<!-- 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>
2. Add a Select Element
<select id="mySelect" multiple></select>
3a. Initialize with JS Data (supports unlimited nesting)
$('#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' }
            ]
        }
    ]
});
3b. Or Initialize from HTML Optgroups (1 level of nesting)
<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();
4. Get/Set Values with Standard jQuery
// 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

Configuration Options

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) {}.

Methods

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.

CSS Classes Reference

All classes use the bsms- prefix to avoid collisions. Override any of these in your own stylesheet to customize the look and feel.

Layout
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.
Search & Select All
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.
Groups (Accordion)
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.
Leaf Options
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.
State Classes
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.
Dark Mode

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');
Customization Example
/* 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;
}

Generated DOM Structure

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>