Multi Select

Select multiple values with tag-style chips, search, and keyboard navigation.

Two implementation paths are shown side-by-side throughout. Tom Select handles search, keyboard navigation, and ARIA with a single attribute — use it when the option list is long or the UX must be polished. The Alpine.js variant covers simple checkbox-list multi-select where a full library would be overkill. Choose the lightest tool that satisfies the requirement.

1 — Checkbox List

Pure DaisyUI + Alpine. No extra library. Best when the option count is small and all choices should be visible at once without interaction.

Default

Categories

selected

With Select All

Tech Stack

Disabled

Categories
<div
    x-data="{
        options: [
            { value: 'electronics', label: 'Electronics' },
            { value: 'clothing',    label: 'Clothing'    },
            // …
        ],
        selected: [],
        toggle(v) {
            this.selected.includes(v)
                ? this.selected = this.selected.filter(s => s !== v)
                : this.selected.push(v);
        }
    }"
>
    <fieldset class="fieldset bg-base-100 border border-base-300 rounded-[--radius-sm] p-3 gap-0">
        <legend class="fieldset-legend text-xs font-medium px-1">Categories</legend>
        <template x-for="opt in options" :key="opt.value">
            <label class="fieldset-label cursor-pointer py-1">
                <input
                    type="checkbox"
                    class="checkbox checkbox-sm checkbox-primary"
                    :checked="selected.includes(opt.value)"
                    @change="toggle(opt.value)"
                />
                <span class="text-sm" x-text="opt.label"></span>
            </label>
        </template>
    </fieldset>
</div>

2 — Tag Pills with Dropdown

Selected values render as removable pill badges above a dropdown panel. Alpine handles open/close; @click.outside closes the panel on any outside interaction without custom JS.

Default

Max 3 selections

/ selected

<div
    x-data="{
        open: false,
        options: [ { value: 'react', label: 'React' }, /* … */ ],
        selected: [],
        toggle(v) {
            this.selected.includes(v)
                ? this.selected = this.selected.filter(s => s !== v)
                : this.selected.push(v);
        },
        label(v) { return this.options.find(o => o.value === v)?.label ?? v; }
    }"
    @click.outside="open = false"
    class="relative"
>
    <!-- Trigger -->
    <div class="min-h-10 w-full flex flex-wrap gap-1.5 items-center px-3 py-2
                border border-base-300 rounded-[--radius-sm] bg-base-100 cursor-pointer"
         @click="open = !open">
        <template x-for="v in selected" :key="v">
            <span class="badge badge-soft badge-primary badge-sm gap-1 pr-1">
                <span x-text="label(v)"></span>
                <button type="button" @click.stop="toggle(v)">✕</button>
            </span>
        </template>
    </div>

    <!-- Panel -->
    <div x-show="open" x-collapse
         class="absolute z-20 left-0 right-0 mt-1 bg-base-100 border
                border-base-300 rounded-[--radius-sm] shadow-md overflow-hidden">
        <ul class="py-1 max-h-52 overflow-y-auto">
            <template x-for="opt in options" :key="opt.value">
                <li>
                    <label class="flex items-center gap-2.5 px-3 py-1.5 text-sm
                                  cursor-pointer hover:bg-base-200">
                        <input type="checkbox" class="checkbox checkbox-xs checkbox-primary"
                               :checked="selected.includes(opt.value)"
                               @change="toggle(opt.value)" />
                        <span x-text="opt.label"></span>
                    </label>
                </li>
            </template>
        </ul>
    </div>
</div>

3 — Searchable (Tom Select)

Tom Select wired to a plain <select multiple>. Built-in search, ARIA, and keyboard navigation come for free. Use plugins: ['remove_button'] to add per-chip remove buttons without extra markup.

Searchable — basic

Grouped options

<!-- HTML -->
<select id="my-select" name="items[]" multiple placeholder="Search and select…">
    <option value="react" selected>React</option>
    <option value="vue">Vue</option>
    <!-- … -->
</select>

<!-- JS (after Tom Select CSS + JS are loaded) -->
<script>
new TomSelect('#my-select', {
    plugins: ['remove_button'],
    maxOptions: 20,
    placeholder: 'Search and select…',
});
</script>

<!-- Opt-in flag in PHP front-matter -->
<?php
$useTomSelect = true;
?>

4 — States: Empty & Error

Required field validation and empty-selection states for use in controlled form submissions.

Empty

Select options…

Error

Select at least one…

Please select at least one category.

Loading